程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

STM32F103定时器配置

一、定时器

定时器,顾名思义,就是用于定时或计数,它其实就是一个加1计数器。

1.1 定时器类型

STM32f103有三类定时器:

类型 编号 总线 功能
高级定时器 TIM1、TIME8 APB2 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器 TIM2、TIM3、TIM4、TIM5 APB1 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器 TIM6、TIM7 APB1 拥有定时中断、主模式触发DAC的功能

每个通用定时器TIMx功能:

  • 位于低速的APB1总线上;
  • 16位向上、向下、向上/向下自动装载计数器(TIMx_CNT);
  • 16位可编程预分频器(TIMx_PSC),计数器的时钟频率的分频系数为1~65535之间的任意数值;
  • 4个独立通道(TIMx_CH1~4),这些通道可以用来作为:
    • 输入捕获;
    • 输出比较;
    • PWM生成;
    • 单脉冲模式输出;
  • 可使用外部信号(TIMx_ETR)控制定时器和定时器互联的同步电路;
  • 如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
    • 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或内部外部触发);
    • 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数);
    • 输入捕获;
    • 输出比较;
    • 支持针对定位的增量(正交)编码器和霍尔传感器电路;
    • 触发输入作为外部时钟或者按周期的电流管理;

STM32的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。

使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源。

1.1.1 高级定时器框图
1.1.2 通用定时器框图
1.1.3 基本定时器框图

1.2 计数器模式

通用定时器可以向上计数、向下计数、向上向下双向计数模式。

  • 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件;
  • 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件;
  • 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。

具体如下图所示:

img

1.3 功能介绍

1.3.1 定时器中断
在这里插入图片描述
1.3.2 输出比较

输出比较可以通过比较CNTCCRx寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。

输出比较模式:

模式 描述
冻结 CNT=CCR时,REF保持为原状态
匹配时置有效电平 CNT=CCR时,REF置有效电平
匹配时置无效电平 CNT=CCR时,REF置无效电平
匹配时置电平翻转 CNT=CCR时,REF电平翻转
强制为无效电平 CNT与CCR无效,REF强制为无效电平
强制为有效电平 CNT与CCR无效,REF强制为有效电平
PWM模式1 向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平
向下计数:CNT>CCR时,REF置无效电平,CNT≤CRR时,REF置有效电平
PWM模式2 向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平
向下计数:CNT>CCR时,REF置有效电平,CNT≤CRR时,REF置无效电平
1.3.3 PWM基本结构
在这里插入图片描述

PWM频率 :\(F_{req} = T_{clk} / (PSC + 1) / (ARR + 1)\)

PWM占空比:\(Duty = CCR / (ARR + 1)\)

PWM分辨率:\(Peso = 1 / (ARR + 1)\)

二、定时器相关寄存器

2.1 控制寄存器(TIMx_CR1

想要使用定时器功能,首先要使能定时器。使能定时器主要用到控制寄存器1TIMx_CR1),此处重点关注第一位,这一位是计数器使能位,给此位设1,使能计数器;

此外我们也需要关注位7 ARPE、位4 DIR

2.2 DMA/中断使能寄存器(TIMx_DIER

定时器的使用,很多情况下都伴随着中断,因此下面要重点关注的是DMA/中断使能寄存器(TIMx_DIER)。同样的,只用关注第一位。该位为更新中断允许位,当该位设置为1时,将允许由于更新事件所产生的中断;

2.3 预分频寄存器(TIMx_PSC

接下来就要确定定时器的时间,定时器的定时是通过频率计算出来的。这里要用到预分频寄存器(TIMx_PSC)。该寄存器用于设置对时钟进行分频,然后提供给计数器,作为计数器的时钟;

2.3.1 计数器计数频率

定时器的时钟频率是\(T_{clk}\)TIMx_PSC即为PSC的值。时钟频率被分频了PSC+1,那么定时器的时钟频率:

\[CK\_CNT = 时钟源频率 / 预分频系数 = \frac{T_{clk}}{PSC + 1} \]

故可知定时器计数值加1所需的时间为:$ \frac{PSC + 1}{T_{clk}}$。

2.3.2 计数器溢出频率

自动重装载值ARR,即TIM_ARR。定时器从0计数到ARR时清零。由第一步已经计算出了被分频了PSC+1的最终定时器的时钟频率为$ \frac{T_{clk}}{PSC + 1}$,这是计数一次的频率,故计数器溢出频率 (或者说定时器溢出频率):

\[CK\_CNT\_OV = 时钟源频率 / 预分频系数 /自动重装载寄存器 = CK\_CNT / (ARR + 1) = T_{clk} / (PSC + 1) / (ARR + 1) \]

2.3.3 计数器溢出时间

计数器溢出时间(或者说定时器溢出时间):

\[T_{out} = 1 / CK\_CNT\_OV = \frac {(PSC + 1) \times (ARR + 1) }{T_{clk}} \]

其中

  • \(T_{clk}\)为定时器的输入时钟频率(单位Mhz),通常为系统时钟频率或者定时器外部时钟频率;
  • ARRTIM_ARR):自动重装载值,是定时器溢出前的计数值;
  • PSCTIMx_PSC):预分频值,是用来降低定时器时钟频率的参数;
  • T_{out}:定时器溢出时间(单位us),一定要注意这个单位是us

2.4 从模式控制寄存器(TIMx_SMCR

从框图中可以看到统通用定时器时钟来源有以下几个:

  • 内部时钟(CK_INT)默认;
  • 外部时钟模式1:外部输入脚(TIx);
  • 外部时钟模式2:外部触发输入(ETR);
  • 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

时钟源的选择,可以通过从模式寄存器(TIMx_SMCR)的第3位来选择;

2.5 自动重装载寄存器(TIMx_ARR

下面是自动重载寄存器。自动装载寄存器(TIMx_ARR)是预先装载的,写或读自动重装载寄存器将访问预装载寄存器;

2.6 状态寄存器(TIMx_SR

如果发生了更新中断,UIF位会被置1

三、定时中断源码

3.1 定时中断初始化步骤

定时中断配置流程如下:

(1) TIMx时钟使能:通过配置RCC_APB1ENR/RCC_APB2ENR寄存器使能TIMx时钟;

(2) 配置TIMx时基单元;

  • 配置TIMx_ARR寄存器自动重装载的值;

  • 配置TIMx_PSC频寄存器预分频系数;

(3) 配置TIMx_DIER寄存器允许更新中断(UIE=1);

(4) 设置NVIC;

(5) 中断处理函数;

  • 设置中断服务函数(包括清除中断标志,清SR寄存器状态标志位(UIF=0))。

(6) 允许TIMx工作:配置TIMx_CR10

3.2 源码实现

3.2.1 TIM_TypeDef

TIMx寄存器结构TIM_TypeDef,在文件stm32f10x_map.h中定义如下:

/*------------------------ TIM ----------------------------*/
typedef struct
{
  vu16 CR1;		  // 控制寄存器1 ;
  u16  RESERVED0; 
  vu16 CR2;		  // 控制寄存器2 ;
  u16  RESERVED1; 
  vu16 SMCR;	  // 从模式控制寄存器 ;
  u16  RESERVED2;
  vu16 DIER;	  // DMA/中断使能寄存器 ;
  u16  RESERVED3;
  vu16 SR;		  // 状态寄存器 ;
  u16  RESERVED4;
  vu16 EGR;		  // 事件产生寄存器 ;
  u16  RESERVED5;
  vu16 CCMR1;	  // 捕获/比较模式寄存器1 ;
  u16  RESERVED6;
  vu16 CCMR2;	  // 捕获/比较模式寄存器2 ;
  u16  RESERVED7;
  vu16 CCER;	  // 捕获/比较使能寄存器 ;
  u16  RESERVED8;
  vu16 CNT;		  // 计数器寄存器 ;
  u16  RESERVED9;
  vu16 PSC;		  // 预分频寄存器 ;
  u16  RESERVED10;
  vu16 ARR;		  // 自动重装载寄存器 ;
  u16  RESERVED11;
  vu16 RCR;		  // 周期计数寄存器 ;
  u16  RESERVED12;
  vu16 CCR1;	  // 捕获/比较寄存器1 ;
  u16  RESERVED13;
  vu16 CCR2;	  // 捕获/比较寄存器2 ;
  u16  RESERVED14;
  vu16 CCR3;	  // 捕获/比较寄存器3 ;
  u16  RESERVED15;
  vu16 CCR4;	  // 捕获/比较寄存器4 ;
  u16  RESERVED16;
  vu16 BDTR;	  // 刹车和死区寄存器 ;
  u16  RESERVED17;
  vu16 DCR;		  // DMA控制寄存器 ;
  u16  RESERVED18;
  vu16 DMAR;	  // 连续模式的DMA地址寄存器 ;
  u16  RESERVED19;
} TIM_TypeDef;

在前面我们已经对TIM_TypeDef结构体中定义的大部分寄存器进行了详细的介绍,那么我们如何编码去初始化这些寄存器呢?

3.2.2 TIMx初始化

定时器初始化函数TIM_Int_Init定义如下:

volatile   TIM_TypeDef  *TIMx[8] = {TIM1,TIM2,TIM3,TIM4,TIM5,TIM6,TIM7,TIM8}; //定义8个指针数组保存 TIMx 的地址
/**************************************************************************************************************
 *
 *		 Description:   高级定时器1和8   APB2预分频系数=1 则计数器的时钟频率为 APB2  否则APB2*2
 *						 通用定时器2~7    APB1预分频系数=1 则计数器的时钟频率为 APB1  否则APB1*2
 *       Parameter  :    timx           TIMER1~TIMER8
                         DEFAULT_PSC    默认预分频系数 
                                        计数器的时钟频率 =  Fclk/(PSC[15:0]+1)     
                         time           中断时间 = (arr+1)/计数器的时钟频率/1000   单位ms
 *
 **************************************************************************************************************/ 
void TIM_Int_Init(TIMn timn,u32 time)                  
{
    u16 arr;							    //存放自动重装载的值
	u8 fclk;                                //存放定时器时钟频率  MHZ
	u16 DEFAULT_PSC=DEFAULT_PSC_MS;
    //u16 DEFAULT_PSC=DEFAULT_PSC_US;    	定时器中断us秒  
	if(timn==0||timn==7) 	                //定时器1或8   
	{
		 fclk = 72;                        //默认APB2 1倍频
	}
	else 
	{
	     fclk =72;						   //默认APB1 2倍频
	}
	arr = time*fclk*1000/(DEFAULT_PSC+1) -1;     //存放自动装载的值       定时器中断ms  自增1 1ms
	//arr = time*fclk/(DEFAULT_PSC+1)-1;           //存放自动装载的值      定时器中断us  自增1 1us  
	if(timn==0)                        //定时器1
	{
	  RCC->APB2ENR |=1<<11;                 //高级定时器1时钟使能  
	}
	else if(timn==1)                   //定时器2
	{
	  RCC->APB1ENR |=1<<0;                  //定时器2时钟使能  
	}          
    else if(timn==2)                   //定时器3
	{
	  RCC->APB1ENR |=1<<1;                  //定时器3时钟使能  
	}       
	else if(timn==3)                   //定时器4
	{
	  RCC->APB1ENR |=1<<2;                  //定时器4时钟使能  
	}   	              
    else if(timn==4)                   //定时器5
	{
	  RCC->APB1ENR |=1<<3;                  //定时器5时钟使能
	}    
	else if(timn==5)                   //定时器6
	{
	  RCC->APB1ENR |=1<<4;                  //定时器6时钟使能  
	}
	else if(timn==6)                   //定时器7
	{
	   RCC->APB1ENR |=1<<5;                  //定时器7时钟使能  
	}
	else
	{
	   RCC->APB2ENR |=1<<13;                  //定时器8时钟使能   
	}
	                                      
    TIMx[timn]->PSC  = DEFAULT_PSC;       //预分频值
    TIMx[timn]->ARR  = arr;			    //重新装载的值
    TIMx[timn]->DIER = 1<<0;				//允许更新中断   UIE=1;
   	TIMx[timn]->CR1  = 1<<0;              //使能计数器 开始计数
}
3.2.3 设置NVIC

在《STM32F103嵌套向量中断控制器》我们介绍了定时器中断包括:

TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
TIM5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler

比如我们使能中断TIM1_UP_IRQnTIM1更新中断),设置中断优先级分组为2,占优先级为0,响应优先级为0

STM32_NVIC_Init(2, TIM1_UP_IRQn ,0 ,0);		       // 中端使能
3.2.4 中断处理函数

这里定义了定时器相关的部分中断的处理函数,如下:

/*******************************************************************************
* Function Name  : TIM1_UP_IRQHandler
* Description    : This function handles TIM1 overflow and update interrupt 
*                  request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM1_UP_IRQHandler(void)				    //定时器1溢出中断
{
	//**********************自定义用户任务****************************//

    
	//*****************************************************************//
	TIM1->SR &=~(1<<0);                       //清中断标志   必须  (置0清 非写1)
}

/*******************************************************************************
* Function Name  : TIM2_IRQHandler
* Description    : This function handles TIM2 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM2_IRQHandler(void)
{
	//**********************自定义用户任务****************************//

    
	//*****************************************************************//
	TIM2->SR &=~(1<<0);                       //清中断标志
}

/*******************************************************************************
* Function Name  : TIM3_IRQHandler
* Description    : This function handles TIM3 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM3_IRQHandler(void)
{
   //**********************自定义用户任务****************************//

	//*****************************************************************//
	TIM3->SR &= ~(1<<0);                       //清中断标志
}

/*******************************************************************************
* Function Name  : TIM4_IRQHandler
* Description    : This function handles TIM4 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM4_IRQHandler(void)
{
    //**********************自定义用户任务****************************//

	//*****************************************************************//
	TIM4->SR &= ~(1<<0);                       //清中断标志

}
/*******************************************************************************
* Function Name  : TIM5_IRQHandler
* Description    : This function handles TIM5 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM5_IRQHandler(void)
{
   //**********************自定义用户任务****************************//

	//*****************************************************************//
	TIM5->SR &= ~(1<<0);                       //清中断标志
}
/*******************************************************************************
* Function Name  : TIM6_IRQHandler
* Description    : This function handles TIM6 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM6_IRQHandler(void)
{
   //**********************自定义用户任务****************************//

	//*****************************************************************//
	TIM6->SR &= ~(1<<0);                       //清中断标志
}
/*******************************************************************************
* Function Name  : TIM7_IRQHandler
* Description    : This function handles TIM7 global interrupt request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM7_IRQHandler(void)
{
   //**********************自定义用户任务****************************//

	//*****************************************************************//
	TIM7->SR &= ~(1<<0);                       //清中断标志
}

/*******************************************************************************
* Function Name  : TIM8_UP_IRQHandler
* Description    : This function handles TIM8 overflow and update interrupt 
*                  request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM8_UP_IRQHandler(void)
{
	//**********************自定义用户任务****************************//

    
	//*****************************************************************//
	TIM8->SR &=~(1<<0);                       //清中断标志   必须  (置0清 非写1)
}

3.3 实现功能

这里我们实现一个很简单的功能,每隔1s,串口发送一条数据。

3.3.1 main函数实现
int main()
{
   u16 i=0;
   STM32_Clock_Init(9);                           //系统时钟初始化
			
   STM32_NVIC_Init(2,USART1_IRQn,0,1);		      //串口中断优先级初始化,其中包括中断使能
   usart_init(USART_1,115200);				      //串口1初始化,波特率115200 映射到PA9 PA10

   // LED1初始化
   gpio_init(PA8,GPO_SpeedMax_50,HIGH);              //PA8接入LED1 

   STM32_NVIC_Init(2, TIM1_UP_IRQn  ,0,0);		     // TIM1更新中端使能
   TIM_Int_Init(TIMER1,1000);                        //TIM1计数到1000ms发生中断

   while(1)
   {
	 delay_ms(1000);
   }
}
3.3.2 TIM1_UP_IRQHandler函数实现

修改TIM1_UP_IRQHandler中断处理函数,添加串口输出:

/*******************************************************************************
* Function Name  : TIM1_UP_IRQHandler
* Description    : This function handles TIM1 overflow and update interrupt 
*                  request.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM1_UP_IRQHandler(void)				    //定时器1溢出中断
{
	//**********************自定义用户任务****************************//
	usart_sendStr(USART_1, "Time1 test");
	//*****************************************************************//
	TIM1->SR &=~(1<<0);                       //清中断标志   必须  (置0清 非写1)
}
3.3.3 测试

编译程序并下载测试:

可以看到1s串口输出一条数据。

四、源码下载

源码下载路径:stm32f103

参考文章

[1] STM32F103定时器配置

[2] 正点原子STM32F103学习笔记(十)——定时器、PWM

posted @ 2024-12-04 00:18  大奥特曼打小怪兽  阅读(886)  评论(0)    收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步