其他分享
首页 > 其他分享> > STM32F103(七)——通用定时器的说明与功能

STM32F103(七)——通用定时器的说明与功能

作者:互联网

前言

本节我们会对STM32的通用定时器功能进行说明和介绍,也是对 STM32(六) 与STM32(七)进行一个总结说明

———————————————————————————————————————————

目录

1. —————   通用定时器的介绍

2. —————   定时器初始化函数

3. —————   PWM波

———————————————————————————————————————————

一. 定时器的介绍

——————————————

1. 什么是通用定时器?

STM32F1 的通用定时器是由一个通过可编程预分频器(PSC)驱动的16位自由装载计数器(CNT)构成,通用  TIMx(2、3、4、5)定时器  可用于测量输入信号的脉冲长度(输入捕获)或者   产生输出波形 (  输出比较  和  PWM  )等

——————————————

2. 定时器的功能

  (1)16位 向上 向下  自动装载计数器     (TIMx_CNT)

  (2)16位可编程(可实时修改)  预分频(TIMx_PSC)

  (3) 4 个独立通道( TIMx_CH1~4 ),这些通道可以用来作为:

            PWM  ,输入捕获 , 输出比较 , 单脉冲模式输出

  __________________________   

3. 产生中断的条件

   A .更新:计数器向上溢出 / 向下溢出,计数器初始化 ( 通过软件或者内部 / 外部触发 )
   B .触发事件 ( 计数器启动、停止、初始化或者由内部 / 外部触发计数 )
   C .输入捕获
   D .输出比较
   E .支持针对定位的增量 ( 正交 ) 编码器和霍尔传感器电路
   F .触发输入作为外部时钟或者按周期的电流管理
————————————————

4. TIM的时钟源

  这里,定时器的时钟来源有 4 个:
  (1)内部时钟( CK_INT )
  (2)外部时钟模式 1 :外部输入脚( TIx )
  (3)外部时钟模式 2 :外部触发输入( ETR )
  (4)内部触发输入( ITRx ):使用 A 定时器作为 B 定时器的预分频器( A 为 B 提供时钟)
————————————————
比较需要注意的是  TIMx_CNT 寄存器,该寄存器是定时器的计数器,该寄存器存储了当前 定时器的计数值,接下来我们看看下面的流程图

 ——————————————————————————————————————————

二. 定时器初始化函数

_______________________________

我们来过一遍初始化的大致流程

(1)不管是什么外设,只要使用,我们就一定需要让对应的时钟使能

(2)初始化函数(定时器)

(3)开启相应的中断,配置NVIC

(4)使能定时器

(5)编写中断服务函数,在上面中断时使用
————————————————

首先肯定是要定义我们的函数,在这,我们定义了一个填入两个参数的函数

void TIM3_Int_Init(u16 arr,u16 psc);

arr : 自动重装载值    psc : 预分频数

关于时钟的计算方法 : 

使用SystenInit函数初始化的时候,各时钟频率如下:
SYSCLK = 72M
AHB时钟 = 72M
APB1时钟=36M
所以APB1的分频系数=AHB/APB1=2
由此可得CK_INT的时钟频率为2*36M = 72M.
计数器的最终的频率还需要经过PSC预分频计算才能得到

生成时间主要由 TIMx_PSC 和 TIMx_ARR两个寄存器值决定,这个也就是定时器的周期

我们先设置 TIMx_ARR寄存器值为 9999,即当 TIMx_CNT从 0开始计算,刚好等于 9999时生成事件,总共计数 10000次,那么如果此时时钟源周期为 100us即可得到刚好 1s的定时周期。
接下来问题就是设置 TIMx_PSC寄存器值使得 CK_CNT 输出为 100us 周期(10000Hz)的时钟。预分频器的输入时钟 CK_PSC为 90MHz,所以设置预分频器值为(9000-1)即可满足
————————————————

接下来正式进入我们的配置流程

——————————--——————

1. 时钟使能

void TIM3_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

我们在这上面多加了几个定义,这是为了方便我们之后在下面的初始化函数,一样的,因为我们接下来需要使用的是通用定时器3,所以我们使能 TIM3 (要注意他是挂在哪一个通道上的)

————————————————

2. 初始化定时器

TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值

TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值

TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式

TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位

我们前面说过我们的 arr 与 psc ,我们来看看他的计数模式

(1) 向上计数模式 : 计数器从 0 计数到 自动加载值(arr),然后产生一个计数器溢出事件,                                          然后重新从 0 开始计数

(2)向下计数模式 : 计数器从 arr 开始计数到 0 ,然后产生一个计数器溢出事件,然后重新从 0                                       开始计数

(3)向 上/下 计数模式 :计数器从 0 开始 计数到 arr - 1 ,产生一个溢出事件,然后从这个值开                                              始计数到 1,产生一个溢出事件,然后再从 0 开始计数 

——————————————

3. 开启中断,配置NVIC 

    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器

   下面的NVIC我们在前面已经说明过了,我们来看看  

TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );

static void TI3_Config(TIM_TypeDef* TIMx, uint16_t TIM_ICPolarity, uint16_t TIM_ICSelection,uint16_t TIM_ICFilter);

我们可以看到 这个函数里面要我们填三个参数 : TIMx(通道),中断模式(我们在这选择了更                                                                                    新中断), 使能

___________________________________

4. 使能定时器

TIM_Cmd(TIM3, ENABLE);  //使能TIMx	

————————————————————

5. 配置中断函数

void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
		LED1=!LED1;
		}
}

这里我们只是给出一个例子,我们对他进行一下说明:

我们检查我们的中断标志位  TIM_GetITStatus 

如果我们检测到他是1(发生中断),就让 LED灯改变他的状态(亮/灭);当然,我们最好都是要清除我们的标志位的,不然他就卡死在里面了   TIM_ClearITPendingBit   

我们一般把他放在最后

————————————————

这样我们的定时器配置就完成了,我们来看看怎么样使用他

 int main(void)
 {		
 
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 //串口初始化为115200
 	LED_Init();			     //LED端口初始化
	TIM3_Int_Init(4999,7199);//10Khz的计数频率,计数到5000为500ms  
   	while(1)
	{
		LED0=!LED0;
		delay_ms(200);		   
	}	 

 
}	 

这里的 LED0 是与 LED1 进行一个对比参照,来看看我们的定时器是否准确

——————————————————————————————————————————

三. PWM 波

——————————————

1. 什么是 PWM?

脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

——————————————

2. PWM是怎么产生的?

说到这个,我们就要上图说明了(STM(七))

 ——————

随着计数值的累加,就会达到我们之前设定的  橙线,等到到达后,就会发出一个溢出信号,然后计数值清零,每一个循环,都代表  一个PWM周期


PWM的一个周期 : 
    定时器从0开始向上计数
    当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
    t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
    当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程
    至此一个PWM周期完成
 

而接下来的问题,就是如何在这一个周期内产生高低电平的变化

——占空比的标志值 ——(所以就有我们的   红线,CCRx决定占空比)当超过CCRx,电平从0变回1(变成 0 还是 变成 1,这是靠我们后面配置设置的,这边算是举个例子)
————————————

下面是他的输出流程

PWM工作过程

 ——————————

3. PWM库函数配置

PWM的配置就是在 TIM(定时器)配置的基础之上

——————————

首先还是定义一下初始化函数,和TIM前面一样,都需要配置 arr 与 psc

void TIM3_PWM_Init(u16 arr,u16 psc)

——————————

1. 使能定时器(定义结构体)

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

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);	//使能定时器3时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  
    //使能GPIO外设和AFIO复用功能模块时钟

我们需要用灯来展示我们的 PWM 的效果,所以打开了 GPIO ,在后面就不做介绍了

——————————

   //设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
 
   //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = arr; 
   //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 
   //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

——————————

2. 配置 PWM 模式

	//初始化TIM3 Channel2 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据T指定的参数初始化外设TIM3 OC2

这边的极性的意思,也就是 设置有效为高/低电平,这个需要我们与上面的定时器模式进行配合

 STM32中的 PWM模式只是区别什么时候是有效电平,但并没有确定是高电平有效还是低电平有效,而哪个有效则是极性的设置

——————————

3.使能通道

操作TIMx_CCER的CC1P位,修改通道极性

void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
	TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR2上的预装载寄存器
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3


——————————————

配置完后,我们来看看怎么来使用他


 int main(void)
 {		
 	u16 led0pwmval=0;
	u8 dir=1;	
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 	
    //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	     //串口初始化为115200
 	LED_Init();			     //LED端口初始化
 	TIM3_PWM_Init(899,0);	 //不分频。PWM频率=72000000/900=80Khz
   	while(1)
	{
 		delay_ms(10);	 
		if(dir1 == 1)// dir本来就是赋值为1
             led0pwmval++;
		else 
             led0pwmval--;

 		if(led0pwmval>300)dir=0;
		if(led0pwmval==0)dir=1;										 
		TIM_SetCompare2(TIM3,led0pwmval);		   
	}	 
 }

——————————

TIM_SetCompare2(TIM3,led0pwmval);        //改变比较值TIM3->CCR2达到调节占空比的效果

我们的比较值是一个变量,这样就可以不停的更改

——————————————————————————————————————————

标签:STM32F103,TIM3,定时器,通用,TIMx,NVIC,TIM,PWM
来源: https://blog.csdn.net/ArtoriaLili/article/details/122554150