STM32:内部中断---通用定时器定时中断

通用定时器定时中断

定时器介绍

通用定时器TIMx是带预分频器的十六位定时器,内部预分频器(PSC),计数器(CNT),自动重装载寄存器(ARR)均是十六位的,CNT定时计数方式可以是向上,向下,双向计数。

结构:
由时钟源选择,时基单元,输入捕获,比较输出四大功能单元构成

主要功能:

● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意
数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理

通用寄存器:TIM2,TIM3,TIM4,TIM5
高级定时器:TIM1,TIM8
基本定时器:TIM6,TIM7

二. 定时时钟源选择

在这里插入图片描述

TIMx的定时时钟来源有内部时钟CK_INT,外部触发沿时钟ETR,内部触发时钟ITRx,外部时钟源TIx。作为定时应用时,需要选择一个时钟频率固定,已知的时钟源作为定时器的定时时钟。在上面四种时钟上,只有CK_INT的时钟频率是固定且已知的,所以一般选择CK_INT作为定时用的时钟源。

注意:

STM32定时器是作为片内外设挂接在外设总线APB1和APB2上,其内部时钟CK_INT来自定时器所挂接的总线APB1或APB2上。但是不能直接来自外设总线APB1或APB2,而是来自输入为APB1或APB2外设总线时钟的一个倍频器。如图,在时钟树中可以清晰的体现。(补充: 预分频系数为1时,频率不变,否则x2).
在这里插入图片描述

三. 时基单元

时基单元由计数器寄存器(TIMx_CNT),预分频器寄存器 (TIMx_PSC),自动装载寄存器 (TIMx_ARR)组成。PSC和ARR均带有影子寄存器,其控制作用的主要是影子寄存器。
在这里插入图片描述

  • CK_CNT=CK_PSC/(PSC+1).分频系数由PSC的值决定,PSC取值0~65535.
  • CNT是十六位寄存器,可选择向上,向下,双向计数
  • ARR是自动重装寄存器,用于保存定时器定时初值。

补充:

什么是影子寄存器?

影子寄存器的引入是ARM的一个特点(X86,PowerPC都没有)。我们知道,ARM有16个通用寄存器,这16个通用寄存器在指令中使用4个bit来标识,但是在不同的模式下,同样的4个bit 指向不同的物理寄存器,这些不同的物理寄存器就被称之为影子寄存器。不同的通用寄存器的影子寄存器个数也不相同,有的没有,有的只有1个,有的多达5个。要记住一点:所有的影子寄存器都是一个实际存在的物理寄存器

四. 计数模式

向上计数模式: 计数器是从0开始向上加1计数到自动重装载寄存器ARR的值,在经过一个计数时钟,CNT的值溢出回到0再重新计数,并产生一个计数上溢更新事件。

向下计数模式: 计数器从自动重装载寄存器ARR的值开始向下减1计数到0,在经过1个计数时钟,CNT的值溢出回归到ARR开始重新计数,并产生一个计数下溢更新事件。

(向上计数模式和向下计数模式总共经历(ARR+1)次时钟)

双向计数模式: 计数器首先从0开始加1计数到ARR的值,同时产生上溢更新事件,然后向下减一计数直到0,同时产生下溢更新事件,随后进入下一次定时计数。在产生下溢更新事件的同时,若使能了更新更新中断允许,将产生更新中断请求。总共经历(ARR+ARR)个时钟。

五. 定时时间计算

六. 代码分析

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t num;
int main(void)
{
	OLED_Init();
	Init_Timer();
	OLED_ShowString(1, 1, "Num:");
	while(1)
	{
		OLED_ShowSignedNum(1, 5, num,5);
	}
}

void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

主函数代码主要是使用定时器实现计数。

①. 函数Init_Timer()
void Init_Timer(void)
{
	NVIC_InitTypeDef NVIC_InitTypeStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  //定时器2挂载在APB1上
	
	TIM_InternalClockConfig(TIM2); //选择内部时钟源
	
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //选择不分频.该参数表示时钟分频,时钟频率越低,周期越长,采样点就越多,
	                                                          //能达到一定程度上的滤波作用,但是相应的延迟就会更大
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;   //向上计数
	TIM_TimeBaseInitStructure.TIM_Period=10000-1;        //ARR自动重装值 (总数)
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;     //PSC预分频器的值 (一周期多少次)
	//为什么TIM_Period和TIM_Prescaler都减一
	//因为预分频器与计数器都有一个数的偏差,所有都需要减一
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;   //重复计数器的值(高级TIM才有)
	
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);     //手动把更新中断的标志位清除
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeStructure.NVIC_IRQChannel=TIM2_IRQn ;
	NVIC_InitTypeStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitTypeStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitTypeStructure.NVIC_IRQChannelSubPriority=1;
	
	NVIC_Init(&NVIC_InitTypeStructure);
	
	TIM_Cmd(TIM2,ENABLE);
}

函数的主要逻辑是:先将定时器挂载在APB2总线上,然后打开内部时钟源,再配置定时器各参数并进行初始化。完成之后,再配置NVIC进行初始化,最后使能TIM时钟。

Ⅰ. 函数TIM_InternalClockConfig(TIM2)

/**
  * @brief  Configures the TIMx internal Clock
  * @param  TIMx: where x can be  1, 2, 3, 4, 5, 8, 9, 12 or 15
  *         to select the TIM peripheral.
  * @retval None
  */
void TIM_InternalClockConfig(TIM_TypeDef* TIMx)
{
  /* Check the parameters */
  assert_param(IS_TIM_LIST6_PERIPH(TIMx));
  /* Disable slave mode to clock the presc-aler directly with the internal clock */
  //关闭从模式,使预分频器直接与内部时钟同步
  TIMx->SMCR &=  (uint16_t)(~((uint16_t)TIM_SMCR_SMS));
}

在从模式控制寄存器SMCR中说明
在这里插入图片描述

定时器参数配置

TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
//选择不分频.该参数表示时钟分频,时钟频率越低,周期越长,采样点就越多,
//能达到一定程度的滤波作用,但是相的延迟就会更大
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; 
//向上计数
TIM_TimeBaseInitStructure.TIM_Period=10000-1;        
//ARR自动重装值 (总数)
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;    
//PSC预分频器的值 (一周期多少次)
//为什么TIM_Period和TIM_Prescaler都减一
//因为预分频器与计数器都有一个数的偏差,所有都需要减一
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;   
//重复计数器的值(高级TIM才有)

现在突然悟出很重要的一点

一般在宏定义的时候,他会直接根据控制寄存器相应的要求来定义具体的值是多少。

例如:
#define TIM_CKD_DIV1                       ((uint16_t)0x0000)
#define TIM_CKD_DIV2                       ((uint16_t)0x0100)
#define TIM_CKD_DIV4                       ((uint16_t)0x0200)

#define TIM_CounterMode_Up                 ((uint16_t)0x0000)
#define TIM_CounterMode_Down               ((uint16_t)0x0010)
#define TIM_CounterMode_CenterAligned1     ((uint16_t)0x0020)

在这里插入图片描述
在这里插入图片描述

TIM_CKD_DIV1表示不分频,根据寄存器的要求位9-8的值为00;TIM_CKD_DIV2表示二分频,位9-8的值为01,十六进制码为0x0100。TIM_CounterMode_Up表示向上计数,根据寄存器要求,位6-5值需要是00,表示边沿计数,然后根据位4(DIR)的值决定向上还是向下,最后向上计数模式定义为0x0000。其他宏定义的参数也是如此。

如何计算周期与频率:

CK_CNT=CK_PSC/(PSC+1);
T=(ARR+1)/CK_CNT;
T=(ARR+1)*(PSC+1)/CK_PSC;

CK_PSC选择的时钟源是CK_INT.

Ⅱ. 函数TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

/**
  * @brief  Initializes the TIMx Time Base Unit peripheral according to 
  *         the specified parameters in the TIM_TimeBaseInitStruct.
  * @param  TIMx: where x can be 1 to 17 to select the TIM peripheral.
  * @param  TIM_TimeBaseInitStruct: pointer to a TIM_TimeBaseInitTypeDef
  *         structure that contains the configuration information for the 
  *         specified TIM peripheral.
  * @retval None
  */
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
  uint16_t tmpcr1 = 0;

  /* Check the parameters */
  assert_param(IS_TIM_ALL_PERIPH(TIMx)); 
  assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
  assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));

  tmpcr1 = TIMx->CR1;  
  //下面两个if语句中都是配置通用寄存器和高级寄存器的模式选择
  if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
     (TIMx == TIM4) || (TIMx == TIM5)) 
  {
    /* Select the Counter Mode */
    tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
  }
 
  if((TIMx != TIM6) && (TIMx != TIM7))
  {
    /* Set the clock division */
    tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
  }
  //将tmpcr1值写入CR1中,配置对应模式
  TIMx->CR1 = tmpcr1;

  /* Set the Autoreload value */
  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
 
  /* Set the Prescaler value */
  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
    
  //配置到高级定时器和TIM15,TIM16,TIM17定时器的重复计数寄存器。因为这部分功能只有这几个定时器有
  if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))  
  {
    /* Set the Repetition Counter value */
    TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
  }

  /* Generate an update event to reload the Prescaler and the Repetition counter
     values immediately */
  //重新初始化计数器,并产生一个更新事件。
  TIMx->EGR = TIM_PSCReloadMode_Immediate;           
}

函数TIM_ClearFlag(TIM2,TIM_FLAG_Update)和TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE)和void TIM_Cmd(TIM_TypeDef TIMx, FunctionalState NewState)*
TIM_ClearFlag(TIM2,TIM_FLAG_Update)清除中断更新标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE)使能中断
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)使能中断计数器

代码比较简单就不过多分析,参考DMA/中断寄存器,和状态寄存器。

注:其余为分析的函数可以参考https://blog.csdn.net/qq_74215816/article/details/139041453?spm=1001.2014.3001.5502

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值