单片机
返回首页

stm32 TIM定时器 PWM脉冲输出[操作寄存器+库函数]

2017-02-06 来源:eefocus

脉冲调制(PWM)是利用微处理器对数字输出来对模拟电路的一种非常有效的技术。简单点说就是对确定频率的信号,调整其占空比。

 

stm32的定时器除了TIM6和TIM7外,其他定时器都可以产生PWM输出。其中高级定时器TIM1和TIM8可以产生多达7路的PWM输出。通用定时器可以产生4路的PWM输出。

 

在stm32 TIM定时器[操作寄存器+库函数]  中我们是通过在中断中,翻转指定引脚的电平。在stm32中可以通过配置一个捕获/比较模式寄存器(TIMx_CCMR),设置通道引脚输出模式为PWM脉冲模式,在计时器计数到捕获/比较模式寄存器的值,指定引脚会输出一个有效电平,这样就可以通过定时器直接产生 PWM脉冲。这种方式下不需要开启中断。

 

  • 这里说有效电平是因为这个电平不一定为1,这个在 捕获/比较使能寄存器(TIMx_CCER)中可以设置有效电平的极性。

  • 指定引脚不是任意的,这个stm32对每个定时器通道有特定的引脚对应 对应关系如下

 

TIMx_CHx 对应的I/O口就是此通道对应的引脚

 

IMG_20120417_143213.jpg

 

可以看出 TIM2的 OC通道 1-4 对应的就是   GPIOA 0-3 

 

此例直接操作寄存器实现 Led灯由暗到亮再由亮到暗的呼吸灯效果。库函数实现用PWM脉冲输出模式,产生4个不同频率的脉冲,让led闪烁。


 

直接操作寄存器

 

通用定时器的每个通道都有6种输出模式,其中有两种PWM模式。通过捕获/比较模式寄存器1(TIMx_CCMR1)设定,由OC1M[2:0]三位决定。6种模式如下:

  • 000:冻结。输出比较寄存器TIMx_CCR1与计数器TIMx_CNT间的比较对OC1REF不起作用;

  • 001:匹配时设置通道1为有效电平。当计数器TIMx_CNT的值与捕获/比较寄存器1 (TIMx_CCR1)相同时,强制OC1REF为高。

  • 010:匹配时设置通道1为无效电平。当计数器TIMx_CNT的值与捕获/比较寄存器1 (TIMx_CCR1)相同时,强制OC1REF为低。

  • 011:翻转。当TIMx_CCR1=TIMx_CNT时,翻转OC1REF的电平。

  • 100:强制为无效电平。强制OC1REF为低。

  • 101:强制为有效电平。强制OC1REF为高。

  • 110:PWM模式1- 在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。

  • 111:PWM模式2- 在向上计数时,一旦TIMx_CNTTIMx_CCR1时通道1为有效电平,否则为无效电平。 

两种PWM模式,区别在于通道的电平极性是相反的。

 

首先需要设定TIMx_CCMR1寄存器:  

TIMx_CCMR1.png

 

OCxM[2:0]已经做了介绍,OC2CE:输出比较2清0使能  OC2PE:输出比较2预装载使能 

通过设定OC2M[2:0]为 110/111 为PWM脉冲输出模式。

 

设定TIMx_CCER寄存器相关位,使能通道输出,还可以设置有效电平极性。

 

最后一个就是调整占空比的关键寄存器,捕获/比较寄存器(TIMx_CCRx),低16位有效,这个寄存器已经使用过,要实现PWM脉冲的占空比可调的原理就是不断改变这个寄存器的值。

 

要实现led亮暗的渐变,PWM的频率不能太低,低于50Hz的时候就会明显感觉到闪烁。这里用8khz的频率,调整PWM输出占空比,从0到不断增大其占空比,再递减为0.

 

代码如下: (system.h 和 stm32f10x_it.h 等相关代码参照 stm32 直接操作寄存器开发环境配置)

User/main.c

#include 	 
#include 'system.h' 
#include 'tim.h' 	

void Gpio_Init(void);

int main(void)
{				  
	u32 var=0,flag=0;

	Rcc_Init(9); 			 //系统时钟设置

	// 相关TIM_x,CCR_x参数定义tim.h文件

	Tim_Init(TIM_3,900,0);  //初始化TIM3定时器,设定重装值和分频值

	Tim_OC_Set(TIM_3,OC_2,7);    //设定TIM3 通道1为PWM输出模式

	Gpio_Init();

	while(1){		

		delay(5000);	  //延时5ms

		if(flag){
			var--;
		}else{
			var++;
		}

		if(var>300) flag = 1; 
		
		if(var == 0) flag = 0;

		Tim_CCR_Set(TIM_3,OC_2,var); 	
	}

}


void Gpio_Init(void)
{
	RCC->APB2ENR|=1<<2;    //使能PORTA时钟 	   	 

	  	
	GPIOA->CRL&=0X0FFFFFFF;//PA7输出
	GPIOA->CRL|=0XB0000000;//复用功能输出 	  
  
}

 

Library/src/tm.c

#include 	 
#include 'tim.h' 


//通用定时器初始化
//参数说明:TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h), arr为自动重装值 ;psc 为时钟预分频数
//要使用定时器的其他函数,必须先调用此函数,因为时钟在这个函数中开启
//TIM3用于PWM输出已测试
//待完善 目前只支持TIM2	
//其他定时器只做了开启时钟处理
void Tim_Init(u8 TIM_x,u16 arr,u16 psc)
{
	switch(TIM_x)
	{
	 	case 1 :{  RCC->APB2ENR |=1<<11; break;  }	     //TIM1高级定时器设置
		case 2 :{										 //TIM2通用定时器设置

			RCC->APB1ENR |=1<<0;

			TIM2->ARR = arr;			//设定自动重装值
			TIM2->PSC = psc;		    //设定预分频值
			TIM2->DIER |= 1<<0;			//允许更新中断
			TIM2->DIER |= 1<<6;			//允许触发中断

 			TIM2->CR1 |= 0x81;			//使能定时器,自动重装允许 	
								
			break;
		}

		case 3 :{

			RCC->APB1ENR |=1<<1;

			TIM3->ARR = arr;			//设定自动重装值
			TIM3->PSC = psc;		    //设定预分频值
			//TIM3->DIER |= 1<<0;			//允许更新中断
			//TIM3->DIER |= 1<<6;			//允许触发中断
			TIM3->CR1 |= 0x81;			//使能定时器			

			break;
		}
		case 4 :{
			RCC->APB1ENR |=1<<2;

			TIM4->ARR = arr;			//设定自动重装值
			TIM4->PSC = psc;		    //设定预分频值
			TIM4->DIER |= 1<<0;			//允许更新中断
			TIM4->DIER |= 1<<6;			//允许触发中断
			TIM4->CR1 |= 0x01;			//使能定时器 						

			break;
		}

		case 5 :{
			RCC->APB1ENR |=1<<3;

			TIM5->ARR = arr;			//设定自动重装值
			TIM5->PSC = psc;		    //设定预分频值
			TIM5->DIER |= 1<<0;			//允许更新中断
			TIM5->DIER |= 1<<6;			//允许触发中断
			TIM5->CR1  |= 0x01;			//使能定时器			

			break;
		}
	 	case 6 :{  RCC->APB1ENR |=1<<4;   break;  }		
	 	case 7 :{  RCC->APB1ENR |=1<<5;   break;  }
	 	case 8 :{  RCC->APB2ENR |=1<<13;  break;  }

	}
}



//捕获比较值设定函数
//参数说明:
//			TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h)
//			OC_x 为选择通道,以确定捕获/比较寄存器(1~4)(定义于tim.h)
//			val   为要设定的捕获/比较寄存器的值
// TIM3,OC_2 用于PWM输出已测试
// 待完善,目前只支持TIM2

void Tim_CCR_Set(u8 TIM_x,u8 OC_x,u32 val)
{
	switch(TIM_x)
	{
	 	case 1 :{ break;}
		case 2 :{

			TIM2->DIER |= 1 << OC_x;			//开启相应允许捕获/比较中断

			switch(OC_x){

				case 1: {
					TIM2 ->CCR1 = val;		 //设置捕获/比较1的值 
					break;
				}

				case 2: {
					TIM2 ->CCR2 = val;		 //设置捕获/比较2的值 
					break;
				}

				case 3: {
					TIM2 ->CCR3 = val;		 //设置捕获/比较3的值 
					break;
				}

				case 4: {
					TIM2 ->CCR4 = val;		 //设置捕获/比较4的值 
					break;
				}
			}
					
			break;
		}

		case 3 :{
			//TIM3->DIER |= 1 << OC_x;			//开启相应允许捕获/比较中断

			switch(OC_x){

				case 1: {
					TIM3 ->CCR1 = val;		 //设置捕获/比较1的值 
					break;
				}

				case 2: {
					TIM3 ->CCR2 = val;		 //设置捕获/比较2的值 
					break;
				}

				case 3: {
					TIM3 ->CCR3 = val;		 //设置捕获/比较3的值 
					break;
				}

				case 4: {
					TIM3 ->CCR4 = val;		 //设置捕获/比较4的值 
					break;
				}
			}			

			break;
		}
		case 4 :{ break;}
		case 5 :{ break;}
	 	case 6 :{ break;}
	 	case 7 :{ break;}
	 	case 8 :{ break;}

	}
}

//定时器通道引脚输出模式设定函数
//参数说明:
//			TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h)
//			OC_x  为选择输出通道选择(1~4)(定义于tim.h)
//			Mode   为选择通道对应引脚输出模式(0~7)
// TIM3,OC_2 用于PWM输出已测试
// 待完善,目前只支持TIM2

void Tim_OC_Set(u8 TIM_x,u8 OC_x,u8 Mode)		  
{
	switch(TIM_x)	
	{
	 	case 1 :{ break;}

		case 2 :{

			switch(OC_x){

				case 1: {
					TIM2 ->CCMR1 |= Mode <<4;     //设定引脚输出模式
					TIM2 ->CCMR1 |= 1<<3;		 //允许预装载

					//TIM2 ->CCER  |= 1<<2;       //引脚输出低电平为有效
					TIM2 ->CCER  |= 1<<0;		 //OC1 输出使能
					break;
				}

				case 2: {
					TIM2 ->CCMR1 |= Mode <<12;     //设定引脚输出模式
					TIM2 ->CCMR1 |= 1<<11;		 //允许预装载

					//TIM2 ->CCER  |= 1<<5;       //引脚输出低电平为有效
					TIM2 ->CCER  |= 1<<4;		 //OC2 输出使能
					break;
				}

				case 3: {
					TIM2 ->CCMR2 |= Mode <<4;     //设定引脚输出模式
					TIM2 ->CCMR2 |= 1<<3;		 //允许预装载

					//TIM2 ->CCER  |= 1<<9;       //引脚输出低电平为有效
					TIM2 ->CCER  |= 1<<8;		 //OC3 输出使能
					break;
				}

				case 4: {
					TIM2 ->CCMR2 |= Mode <<12;     //设定引脚输出模式
					TIM2 ->CCMR2 |= 1<<11;		 //允许预装载

					//TIM2 ->CCER |= 1<<5;       //引脚输出低电平为有效
					TIM2 ->CCER  |= 1<<4;		 //OC1 输出使能
					break;
				}
			}
					
			break;
		}

		case 3 :{

			switch(OC_x){

				case 1: {
					TIM3 ->CCMR1 |= Mode <<4;     //设定引脚输出模式
					TIM3 ->CCMR1 |= 1<<3;		 //允许预装载

					//TIM3 ->CCER  |= 1<<2;       //引脚输出低电平为有效
					TIM3 ->CCER  |= 1<<0;		 //OC1 输出使能
					break;
				}

				case 2: {
					TIM3 ->CCMR1 |= Mode <<12;     //设定引脚输出模式
					TIM3 ->CCMR1 |= 1<<11;		 //允许预装载

					TIM3 ->CCER  |= 1<<5;       //引脚输出低电平为有效
					TIM3 ->CCER  |= 1<<4;		 //OC2 输出使能
					break;
				}

				case 3: {
					TIM3 ->CCMR2 |= Mode <<4;     //设定引脚输出模式
					TIM3 ->CCMR2 |= 1<<3;		 //允许预装载

					//TIM3 ->CCER  |= 1<<9;       //引脚输出低电平为有效
					TIM3 ->CCER  |= 1<<8;		 //OC3 输出使能
					break;
				}

				case 4: {
					TIM3 ->CCMR2 |= Mode <<12;     //设定引脚输出模式
					TIM3 ->CCMR2 |= 1<<11;		 //允许预装载

					//TIM3 ->CCER  |= 1<<5;       //引脚输出低电平为有效
					TIM3 ->CCER  |= 1<<4;		 //OC1 输出使能
					break;
				}
			}
					
			break;
		}
		case 4 :{ break;}
		case 5 :{ break;}
	 	case 6 :{ break;}	
	 	case 7 :{ break;}
	 	case 8 :{ break;}
	}
}

Library/inc/tim.h

#include 

#define  TIM_1  0x01
#define  TIM_2  0x02
#define  TIM_3  0x03
#define  TIM_4  0x04
#define  TIM_5  0x05
#define  TIM_6  0x06
#define  TIM_7  0x07
#define  TIM_8  0x08

#define  OC_1  0x01
#define  OC_2  0x02
#define  OC_3  0x03
#define  OC_4  0x04


void Tim_Init(u8 TIM_x,u16 arr,u16 psc); 
void Tim_CCR_Set(u8 TIM_x,u8 OC_x,u32 val);
void Tim_OC_Set(u8 TIM_x,u8 OC_x,u8 Mode);

这里还需要注意的是 Led的连接方式,我的led是低电平亮的 ,如果你的Led是高电平点亮,可以设置通道引脚输出极性为高电平有效。 在Tim_OC_Set()函数中可以设置 ,此例中选用TIM3的OC2通道,只需要注释 TIM3 ->CCER  |= 1<<5;       //引脚输出低电平为有效 这句代码即可。

 

库函数操作

 

要输出PWM脉冲 必须要 将io 设置为复用推挽    

     

代码如下: main.c 


#include 'stm32f10x.h'

vu16 CCR1_Val = 60000;
vu16 CCR2_Val = 30000;
vu16 CCR3_Val = 15000;
vu16 CCR4_Val = 7500;              

void RCC_Configuration(void);
void GPIO_Configuration(void);
void TIM_Configuration(void);

int main(void)
{
   
  	RCC_Configuration();
  	GPIO_Configuration();
	TIM_Configuration();
	while(1);
}

void TIM_Configuration(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_TimeBaseStructure.TIM_Period = 65535;
	TIM_TimeBaseStructure.TIM_Prescaler = 7199;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);

	//TIM_PrescalerConfig(TIM2,7199,TIM_PSCReloadMode_Immediate);

	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	 //使能TIM输出
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
	TIM_OC2Init(TIM2,&TIM_OCInitStructure);
	TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
	TIM_OC3Init(TIM2,&TIM_OCInitStructure);
 	TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
	TIM_OC4Init(TIM2,&TIM_OCInitStructure);
	
	TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable);
	TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);
	TIM_OC3PreloadConfig(TIM2,TIM_OCPreload_Enable);
	TIM_OC4PreloadConfig(TIM2,TIM_OCPreload_Enable);

	//TIM_ITConfig(TIM2,TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,ENABLE);
	
	TIM_Cmd(TIM2,ENABLE);

}


  
void GPIO_Configuration(void)
{
  	GPIO_InitTypeDef GPIO_InitStructure;

  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;			//设置为复用推挽
  	GPIO_Init(GPIOA , &GPIO_InitStructure); 
}


void RCC_Configuration(void)
{
	/* 定义枚举类型变量 HSEStartUpStatus */
	ErrorStatus HSEStartUpStatus;

  	/* 复位系统时钟设置*/
  	RCC_DeInit();
  	/* 开启HSE*/
  	RCC_HSEConfig(RCC_HSE_ON);
  	/* 等待HSE起振并稳定*/
  	HSEStartUpStatus = RCC_WaitForHSEStartUp();
	/* 判断HSE起是否振成功,是则进入if()内部 */
  	if(HSEStartUpStatus == SUCCESS)
  	{
    	/* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */
    	RCC_HCLKConfig(RCC_SYSCLK_Div1); 
    	/* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */
    	RCC_PCLK2Config(RCC_HCLK_Div1); 
    	/* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */
    	RCC_PCLK1Config(RCC_HCLK_Div2);
    	/* 设置FLASH延时周期数为2 */
    	FLASH_SetLatency(FLASH_Latency_2);
    	/* 使能FLASH预取缓存 */
    	FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
    	/* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */
    	RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
    	/* 使能PLL */ 
    	RCC_PLLCmd(ENABLE);
    	/* 等待PLL输出稳定 */
    	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    	/* 选择SYSCLK时钟源为PLL */
    	RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    	/* 等待PLL成为SYSCLK时钟源 */
    	while(RCC_GetSYSCLKSource() != 0x08);
  	} 
  	/* 打开APB2总线上的GPIOA时钟*/
  	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);
	
}


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 用NE555制作定时器

  • 如何构建一个触摸传感器电路

  • 基于ICL296的大电流开关稳压器电源电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章