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 headerextern 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,定时1sTIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;               //预分频器TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;            //这个是高级定时器才用的,这里不用,给0TIM_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_Hvoid 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 headerextern 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_Hvoid 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.xdnf.cn/news/1555913.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

TM1618数码管控制芯片使用共阳极数码管过程中的问题和解决办法

控制芯片的基本了解 相比于不用控制芯片的电路:这里带2根电源线和3个信号线,共使用了5根线,但可以控制4个8段数码管显示。若是电路直接控制4个8段数码管需要84113个接口,这对于MCU的珍贵引脚简直是浪费。 这里不会出现余晖效应也…

Python编程常用的35个经典案例

Python 的简洁和强大使其成为许多开发者的首选语言。本文将介绍35个常用的Python经典代码案例。这些示例覆盖了基础语法、常见任务、以及一些高级功能。 1.列表推导式 这个例子展示了列表推导式,用于生成FizzBuzz序列。 fizz_buzz_list ["FizzBuzz" i…

ultralytics yolo pose 示例:加载官方pose模型进行推理

Ultralytics YOLO 是计算机视觉和 ML 领域专业人士的高效工具。 安装 ultralytics 库: pip install ultralytics 官方YoLo Pose 模型列表信息: 实现代码如下: from ultralytics import YOLO import cv2 # Load a model ckpt_dir "…

HTB:Ignition[WriteUP]

目录 连接至HTB服务器并启动靶机 1.Which service version is found to be running on port 80? 2.What is the 3-digit HTTP status code returned when you visit http://{machine IP}/? 3.What is the virtual host name the webpage expects to be accessed by? 4.…

详细解释:前向传播、反向传播等

详细解释:前向传播、反向传播等 在机器学习和深度学习中,**前向传播(Forward Propagation)和反向传播(Backward Propagation)**是训练神经网络的两个核心过程。理解这两个概念对于掌握神经网络的工作原理、优化方法以及模型微调技术(如LoRA、P-tuning等)至关重要。以下…

机器人技术基础(1-3章坐标变换)

位置矢量的意思是B坐标系的原点O相对于A坐标系的平移变换后的矩阵: 齐次坐标最后一个数表示缩放倍数: 左边的是T形变换矩阵,右边的是需要被变换的矩阵:T形变换矩阵的左上角表示旋转,右上角表示平移,左下角最…

好用且不伤眼镜的超声波清洗机排名!谁才是清洁小能手?

对于经常佩戴眼镜的人来说,眼镜的日常清洁保养极为关键。传统清洁方式可能导致镜片刮花和残留污渍,鉴于此,眼镜专用的超声波清洗机应运而生,利用超声振动技术深入微细缝隙,彻底扫除污垢与油脂,保护镜片免受…

JavaEE: 数据链路层的奇妙世界

文章目录 数据链路层以太网源地址和目的地址 类型数据认识 MTU 数据链路层 以太网 以太网的帧格式如下所示: 源地址和目的地址 源地址和目的地址是指网卡的硬件地址(也叫MAC地址). mac 地址和 IP 地址的区别: mac 地址使用6个字节表示,IP 地址4个字节表示. 一般一个网卡,在…

Unity3D 单例模式

Unity3D 泛型单例 单例模式 单例模式是一种创建型设计模式,能够保证一个类只有一个实例,提供访问实例的全局节点。 通常会把一些管理类设置成单例,例如 GameManager、UIManager 等,可以很方便地使用这些管理类单例,…

BM1 反转链表

要求 代码 /*** struct ListNode {* int val;* struct ListNode *next;* };*/ /*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可*** param head ListNode类* return ListNode类*/ struct ListNode* ReverseList(struct …

从零开始讲PCIe(10)——事务层介绍

一、事务层概述 事务层在响应软件层的请求时,会生成出站数据包。同时,它也会检查入站数据包,并将其中包含的信息传递到软件层。事务层支持非发布事务的分割事务协议,能够将入站的完成数据包与之前传输的非发布请求相关联。该层处理…

After-kaoyan

知乎 - 安全中心 有态度,有回应,有温度,是跟双鱼相处的基础 我今天跟大家泄漏一个秘密,这个秘密也很简单,就是我每次遇到困难险阻时候我从不退缩,我也不会想着:“算了吧,我做不到&a…

C/C++/EasyX——入门图形编程(5)

【说明】友友们好,今天来讲一下键盘消息函数。(其实这个本来准备和鼠标消息函数放在一起的,但是上一篇三个放在一起,内容就有点多了,只写一个又太单调了,所以键盘消息函数的内容就放在这一篇了 (^&#xff…

用manim实现Gram-Schmidt正交化过程

在线性代数中,正交基有许多美丽的性质。例如,由正交列向量组成的矩阵(又称正交矩阵)可以通过矩阵的转置很容易地进行反转。此外,例如:在由彼此正交的向量张成的子空间上投影向量也更容易。Gram-Schmidt过程是一个重要的算法&#…

Oracle 表空间异构传输

已经有了表空间的数据文件,和元数据dump文件,如何把这个表空间传输到异构表空间中? 查询异构传输平台信息: COLUMN PLATFORM_NAME FORMAT A40 SELECT PLATFORM_ID, PLATFORM_NAME, ENDIAN_FORMAT FROM V$TRANSPORTABLE_PLATFORM O…

LLM大模型:开源RAG框架汇总

前言 本文搜集了一些开源的基于LLM的RAG(Retrieval-Augmented Generation)框架,旨在吸纳业界最新的RAG应用方法与思路。如有错误或者意见可以提出,同时也欢迎大家把自己常用而这里未列出的框架贡献出来,感谢~ RAG应用…

【Python】数据可视化之聚类图

目录 clustermap 主要参数 参考实现 clustermap sns.clustermap是Seaborn库中用于创建聚类热图的函数,该函数能够将数据集中的样本按照相似性进行聚类,并将聚类结果以矩阵的形式展示出来。 sns.clustermap主要用于绘制聚类热图,该热图通…

训练验证器解决数学应用题

人工智能咨询培训老师叶梓 转载标明出处 数学问题解决不仅要求模型能够理解问题的语言表述,还要求其能够准确地执行一系列数学运算,每一步的准确性都至关重要。遗憾的是,现有的语言模型在这一领域的性能远远未能达到人类的水平,它…

[C#]使用onnxruntime部署yolov11-onnx实例分割模型

【官方框架地址】 https://github.com/ultralytics/ultralytics.git 【算法介绍】 在C#中使用ONNX Runtime部署YOLOv11-ONNX实例分割模型,涉及到模型的加载、数据预处理、模型推理和后处理几个关键步骤。 首先,需要确保已经安装了ONNX Runtime的NuGe…

站岗放哨树形dp

前言&#xff1a;好久没有写树上dp了&#xff0c;这儿题目还是挺有意思的 题目地址 #include<bits/stdc.h> #include<iostream> using namespace std;//#define int long long int n; const int N (int)1e510; int e[N],ne[N],h[N],idx 0; int dp[2][N];void add…