关闭

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

452人阅读 评论(0) 收藏 举报
分类:
脉冲调制(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_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
  • 111:PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_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 <stm32f10x_lib.h>	 
#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 <stm32f10x_lib.h>	 
#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 <stm32f10x_lib.h>

#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);
	
}

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    最新评论
    个人资料
    • 访问:59537次
    • 积分:96
    • 等级:
    • 排名:千里之外
    • 原创:6篇
    • 转载:111篇
    • 译文:1篇
    • 评论:2条