当前位置: 首页 > article >正文

STM32定时器(TIM)

目录

一、概述

二、定时器的类型

三、时序

四、定时器中断基本结构

五、定时器定时中断代码

六、定时器外部时钟代码


一、概述

TIM(Timer)定时器

  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHZ计数时钟下可以实现最大559.65s的定时(stm32级联两个16位计数器)
  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

二、定时器的类型

  • 基本定时器:拥有定时中断、主模式触发DAC的功能

如上图,内部时钟的来自RCC的TIMxCLK,这里的频率值一般都是系统的主频72MHZ,也就是基准时钟自动重装载寄存器、PSC预分频器和CNT计数器组成叫做时基单元,通向时基单元的计数基准频率就是72MHZ。

预分频器:如果预分频器写0,就是1分频,输出频率=输入频率=72MHZ;如果预分频器写1,就是2分频,输出频率=输入频率/2=36MHZ;依次类推,所以预分频器的值和实际的分频系数相差了1,预分频系数=预分频器值+1。这个预分频器是16位,最大值就是65535,也就是65536分频。

CNT计数器:对预分频器后的时钟进行计数,计数时钟每来一个上升沿,计数器的值加1。这个计数器也是16位的。计数器从0开始加,加到65535时或目标值时就会从0开始重新加。

自动重装载寄存器:也是16位的,它存的就是我们写入的计数目标值。自动重装值是固定的目标值,当计数值等于自动重装值时,也就是计时时间到了,会产生一个中断信号,并且清零计数器,计数器自动开始下一次的计数计时。

向上的箭头UI:当计数值等于自动重装值时,产生的一个中断信号,我们叫做它更新中断,之后就会通往NVIC,我们再配置好NVIC的定时器通道,那定时器的更新中断就能得到CPU的响应了。

向下的箭头UI:代表会产生一个事件,这里对应的事件就叫做 "更新事件",更新事件不会触发中断,但可以触发内部其他电路的工作

  • 通用定时器:拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。

如上图,通用定时器就比基本定时器复杂的多了。

基本定时器计数模式只有向上计数模式,通用和高级定时器计数模式:向上计数、向下计数、中央对齐。

以预分频器为分界线,最上面的部分就是内外时钟源选择和主从触发模式的结构了。内部时钟由主频72MHZ产生。

外部时钟输入:

TI1F_ED连接的是输入捕获单元的CH1引脚,ED(Edge)就是边沿的意思,上升沿和下降沿均有效。最后,这个时钟还能通过TI1FP1和TI2FP2获得。

编码器接口可以读取正交编码器的输出波形。

TRGO那部分电路,可以把内部的一些事件映射到这个TRGO引脚上,比如也可以把定时器内部的一些事件映射到这里来,用于触发其他定时器、DAC或ADC。

右下部分,捕获/比较寄存器、输出控制。

捕获/比较寄存器是输入捕获和输出比较共用的。

  • 高级定时器暂时不用。

三、时序

  • 预分频器时序

CNT_EN :计数器使能,高电平计数正常运行,低电平计数器停止。

CK_CNT:预分频器输出时钟,也是计数器时钟。

计数器寄存器:在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增。

预分频缓冲器:如果对正在计数的计数频率进行分频,会在计数完成之后,下一次计数才会改变。

计数器计数频率:CK_CNT=CK_PSC/(PSC+1);PSC是预分频的值。

  • 计数器时序

计数器溢出频率CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

时间 t=1/CK_CNT_OV=(PSC + 1)x (ARR + 1)/CK_PSC;

在配置寄存器时PSC一般设置为7200-1;

t=7200*(ARR+1)/72000000=(ARR+1)/10000=(ARR+1)x0.1ms

ARR为自动重装寄存器

  • 计数器无预装时序

如图所示,原来目标值为FF,然后修改为36,然后到达36就会发生更新。

  • 计数器有预装时序

如图所示,多了一个影子寄存器。原来目标值为F5,然后加了影子寄存器,不会到达36发生更新,而到达F5发生更新,而是在下一次更新中断或事件才开始生效。加入影子寄存器的目的是让值得变化和更新事件同步发生,防止在运行途中更改造成错误。

四、定时器中断基本结构

如下图:

五、定时器定时中断代码

  • 配置时钟外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
  • 配置内部时钟
TIM_InternalClockConfig(TIM2);
  • 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;     //滤波频率
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式
TIM_TimeBaseInitStruct.TIM_Period=10000-1;                 //自动重装载寄存器ARR
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;               //预分频器
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
  • 配置中断输出控制
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //启动中断
  • 配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;    //从启动文件找后缀为md.s
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
NVIC_Init(&NVIC_InitStructure);
  • 启动定时器
TIM_Cmd(TIM2,ENABLE);
  • 中断服务函数
void TIM2_IRQHandler(void)
{

	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)   //更新中断就是产生一个中断标志位
	{
		
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update); 
	}


}

完整代码,如下:

Timer.c:

#include "stm32f10x.h"                  // Device header

extern uint16_t Num;
void Timer_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	NVIC_InitTypeDef NVIC_InitStructure;
	//1.配置时钟,用那个外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	//2.内部时钟配置
	TIM_InternalClockConfig(TIM2);
	
	//3.配置时基单元
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;     //滤波频率
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式
	TIM_TimeBaseInitStruct.TIM_Period=10000-1;                 //自动重装载寄存器ARR,定时1s
	TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;               //预分频器
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;            //这个是高级定时器才用的,这里不用,给0

	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	
	
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	//4.配置中断输出控制,打开中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	//5.NVIC配置
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
	NVIC_Init(&NVIC_InitStructure);
	
	//6.启动定时器
	TIM_Cmd(TIM2,ENABLE);
}

void TIM2_IRQHandler(void)
{

	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //更新中断就是产生一个中断标志位
	}


}

Timer.h:

#ifndef _TIMER_H
#define _TIMER_H


void Timer_Init(void);



#endif

main.c:

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

uint16_t Num=0;

int main(void)
{
	
    OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	while(1) 
	{
 
	    OLED_ShowNum(1,5,Num,4);
		OLED_ShowNum(2,5,TIM_GetCounter(TIM2),4);   //TIM_GetCounte()用来获得计数器的值

	}
	
}

运行起来,会有一点问题,就是每次复位Num的值都是从1开始,而不是从0开始。这是因为啥呢?

可以查看时基单元函数TIM_TimeBaseInit(),最后有这么一句话,如下图:绿字意思位:会立即产生一个更新事件去重装载预分频器和重复计数器。

更新时事的同时,也会更新中断,产生中断让预分频器的值立即生效。这是由于预分频缓冲器的存在,我们写入的值不会立即生效,需要在下一计数开始才生效。而系统是想让预分频器的值立即生效,所以会产生一次中断,产生了一个中断标志位,我们需要在中断初始化之前要进行清除。需要用到清除标志位函数,在启动中断之前进行初始化:

TIM_ClearFlag(TIM2,TIM_FLAG_Update);  //清除中断标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //启动中断

这样每次复位Num的值都是从0开始。

六、定时器外部时钟代码

把内部时钟变为外部时钟输入,其他部分基本不变。然后利用对射式红外传感器模拟外部时钟输入。

  • 配置时钟外设

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  • GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;  //PA0口
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
GPIO_Init(GPIOA,&GPIO_InitStructure);
  • 外部时钟配置
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
  • 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;     //滤波频率
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式,向上计数
TIM_TimeBaseInitStruct.TIM_Period=10-1;                 //自动重装载寄存器ARR,需要手动模拟,值不要太大
TIM_TimeBaseInitStruct.TIM_Prescaler=1-1;               //预分频器
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;

TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
  • 配置中断输出控制,打开中断
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
  • NVIC配置
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
NVIC_Init(&NVIC_InitStructure);
  • 启动定时器
TIM_Cmd(TIM2,ENABLE);
  • 中断服务函数和内部时钟的服务函数共用
void TIM2_IRQHandler(void)
{

	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //更新中断就是产生一个中断标志位
	}


}

完整代码,如下:

Timer.c

#include "stm32f10x.h"                  // Device header

extern uint16_t Num;
void Timer_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	NVIC_InitTypeDef NVIC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	//1.配置时钟,用那个外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	//2.外部时钟配置
	TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
	
	//3.配置时基单元
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;     //滤波频率
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //计数方式
	TIM_TimeBaseInitStruct.TIM_Period=10-1;                 //自动重装载寄存器ARR,需要手动模拟,值不要太大
	TIM_TimeBaseInitStruct.TIM_Prescaler=1-1;               //预分频器
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;

	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	//4.配置中断输出控制,打开中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
	//5.NVIC配置
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	
	NVIC_Init(&NVIC_InitStructure);
	
	//6.启动定时器
	TIM_Cmd(TIM2,ENABLE);
}

void TIM2_IRQHandler(void)
{

	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		
		Num++;
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //更新中断就是产生一个中断标志位
	}


}

Timer.h

#ifndef _TIMER_H
#define _TIMER_H


void Timer_Init(void);



#endif

main.c

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

uint16_t Num=0;

int main(void)
{
	
   OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	OLED_ShowString(2,1,"CNT:");
	while(1) 
	{

	   OLED_ShowNum(1,5,Num,4);
		OLED_ShowNum(2,5,TIM_GetCounter(TIM2),4); //获取计数器CNT中计数值

	}
	
}


http://www.kler.cn/news/335413.html

相关文章:

  • jQuery 选择器
  • C++的STL标准模版库容器--list类
  • Linux相关概念和重要知识点(11)(进程调度、Linux内核链表)
  • Hive优化操作(二)
  • 数据结构与算法篇(图)(持续更新迭代)
  • Vue入门-指令学习-v-on
  • 【工具类:FastJsonRedisSerializer】
  • 新技术浪潮下的等保测评:云计算、物联网与大数据的挑战与机遇
  • 【CKA】十四、监控pod的日志
  • 【git】通过配置 `init.defaultBranch`,自定义 Git 初始化时的默认分支名称,避免使用 `master` 并消除相关的警告提示
  • Python水循环标准化对比算法实现
  • rabbitMq-----路由匹配模块
  • 【数据分享】2000—2023年我国省市县三级逐年植被覆盖度(FVC)数据(Shp/Excel格式)
  • verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)
  • 每天一道面试题(22):说一下Dubbo 负载均衡策略?
  • [C++][第三方库][httplib]详细讲解
  • 个人网站,怎么操作才能提升个人网站的流量
  • 泛型中的通配符<?>、<? extends T>、<? super T>的使用场景。ArrayList与LinkedList的区别及适用场景。
  • 【git】配置 Git 的换行符处理和安全性||安装 Ruby
  • 【CV】带你跑通过线检测项目unbox_yolov5_deepsort_counting