TIM定时中断之定时器定时中断定时器外部时钟应用案例

devtools/2024/12/22 9:13:08/

文章目录

  • 前言
  • 一、定时器定时中断应用案例
    • 1.1 应用案例简介
    • 1.2 电路接线图
    • 1.3 应用案例代码
    • 1.4 应用案例分析
      • 1.4.1 初始化定时器
      • 1.4.2 编写定时器中断函数
  • 二、定时器外部时钟应用案例
    • 2.1 应用案例简介
    • 2.2 电路接线图
    • 2.3 应用案例代码
    • 2.4 应用案例分析


前言

提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者

本文主要探讨基于TIM定时中断实现定时器定时中断和定时器外部时钟的功能。


一、定时器定时中断应用案例

1.1 应用案例简介

本案例实现了一个定时器定时中断的功能。定时器使用内部时钟定了一个1秒的时间,每隔1秒申请一次中断,然后在中断函数里执行Num++,最后在OLED上显示Num。

1.2 电路接线图

在这里插入图片描述

1.3 应用案例代码

定时器头文件Timer.h:

#ifndef __TIMER_H
#define __TIMER_Hvoid Timer_Init(void);#endif

定时器实现文件Timer.c:

#include "stm32f10x.h"                  // Device headervoid Timer_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_InternalClockConfig(TIM2);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2, TIM_FLAG_Update);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;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){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
*/

主程序文件main.c:

 #include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num;int main(void)
{OLED_Init();Timer_Init();OLED_ShowString(1, 1, "Num:");while (1){OLED_ShowNum(1, 5, Num, 5);}
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){Num ++;TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

1.4 应用案例分析

首先,初始化定时器。那怎么初始化定时器呢?我们可以看一下下面这个定时中断的整体框架图,我们只需要把这里的每个模块都打通就可以让定时器工作。

在这里插入图片描述
大体上的步骤如下:
第一步,RCC开启时钟。打开时钟后,定时器的基准时钟和整个外设的工作时钟就都会同时打开。
第二步,选择时基单元的时钟源。对于定时中断,我们就选择内部时钟源。
第三步,配置时基单元。包括这里的预分频器、自动重装器以及计数模式等等。
第四步,配置输出中断控制。允许更新中断输出到NVIC。
第五步,配置NVIC。在NVIC中打开定时器中断的通道,并分配一个优先级。
第六步,运行控制。整个模块配置完成后,我们还需要使能一下计数器,要不然计数器是不会运行的。当定时器使能后,计数器就会开始计数了,当计数器更新时,触发中断。

最后我们再写一个定时器的中断函数,这样,这个中断函数每隔一段时间就能自动执行一次了。

以上这些就是初始化定时器的大体思路了。

1.4.1 初始化定时器

先看一下定时器的库函数有哪些,找到stm32f10x_tim.h文件,拖到最后,可以看到这些库函数的数量还是非常多的,我们只挑选本案例需要用到的进行讲解。

void TIM_DeInit(TIM_TypeDef* TIMx);
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

首先是第一个,TIM_DeInit,恢复缺省配置。

第二个,TIM_TimeBaseInit,时基单元初始化。这个函数比较重要,它就是用来配置时基单元的。第一个参数TIMx,选择某个定时器。第二个是结构体TIM_TimeBaseInitTypeDef,里面包含了配置时基单元的一些参数。

接着看第三个函数TIM_TimeBaseStructInit,这个函数可以把结构体变量赋一个默认值。

然后是第四个函数TIM_Cmd,这个是用来使能计数器的。对应的就是定时中断的整体框架图中的运行控制。第一个参数TIMx,选择定时器。第二个NewState,新的状态,也就是选择使能还是失能。使能,计数器就可以运行,失能,计数器就不运行。

然后第五个是函数TIM_ITConfig,这个是用来使能中断输出信号的。对应的就是定时中断的整体框架图中的中断输出控制。第一个参数TIMx,选择定时器。第二个参数TIM_IT,选择要配置哪个中断输出。第三个参数NewState,新的状态,使能还是失能。

接下来看一下TIM_InternalClockConfig、TIM_ITRxExternalClockConfig、TIM_TIxExternalClockConfig、TIM_ETRClockMode1Config、TIM_ETRClockMode2Config以及TIM_ETRConfig这六个函数,这六个函数对应的就是定时中断的整体框架图中的时基单元的时钟选择部分,可以选择RCC内部时钟、ETR外部时钟、ITRx其他定时器、TIx捕获通道这些。

  • TIM_InternalClockConfig函数,选择内部时钟。
  • TIM_ITRxExternalClockConfig函数,选择ITRx其他定时器的时钟。第一个参数TIMx,选择要配置的定时器。第二个参数TIM_InputTriggerSource,选择要接入哪个的其他定时器。
  • TIM_TIxExternalClockConfig函数,选择TIx捕获通道的时钟。第一个参数TIMx,选择要配置的定时器。第二个参数TIM_TIxExternalCLKSource,选择TIx具体的某个引脚。接下来还有TIM_ICPolarity和ICFilter,输入的极性和滤波器。对于外部引脚的波形,一般都会有极性选择和滤波器,这样更灵活一些。
  • TIM_ETRClockMode1Config函数,选择ETR通过外部时钟模式1输入的时钟。参数TIM_ExtTRGPrescaler,外部触发预分频器,这里可以对ETR的外部时钟再提前做一个分频。接下来参数TIM_ExtTRGPolarity和ExtTRGFilter,也是一样,极性和滤波器。
  • TIM_ETRClockMode2Config函数,选择ETR通过外部时钟模式2输入的时钟。它的参数和TIM_ETRClockMode1Config函数一摸一样,这里就不再介绍。对于ETR输入的外部时钟而言,这两个函数是等效的,它们的参数也是一样的,如果不需要触发输入的功能,那两个函数可以互换。
  • TIM_ETRConfig函数,这个不是用来选择时钟的,是用来单独配置ETR引脚的预分频器、极性、滤波器这些参数的。

到这里,关键部分的函数就讲完了。时钟源选择用TIM_InternalClockConfig、TIM_ITRxExternalClockConfig、TIM_TIxExternalClockConfig、TIM_ETRClockMode1Config、TIM_ETRClockMode2Config以及TIM_ETRConfig这六个函数。时基单元,用TIM_TimeBaseInit函数。中断输出控制,用TIM_ITConfig函数。运行控制,用TIM_Cmd函数。这样初始化就基本ok了。

接下来,我们再看几个函数。因为在TIM_TimeBaseInit函数里在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等等,这些参数可能会在初始化之后还需要更改,如果为了改某个参数还要再调一次初始化函数,这样做太麻烦了。所以这里有一些单独的函数可以方便地更改这些关键参数。

比如,TIM_PrescalerConfig函数,单独写预分频值。参数Prescaler就是要写入的预分频值。参数TIM_PSCReloadMode,写入的模式。预分频器有个缓冲器,写入的值是在更新事件发生后才有效的。所以这里有个写入的模式,可以选择是听从安排,再更新事件生效,或者是在写入后,手动产生一个更新事件,让这个值立即生效。不过这些都是细节问题,影响不大,你只需要知道这个是写预分频值的函数就行了。

然后下一个,TIM_CounterModeConfig函数,用来改变计数器的计数模式。参数TIM_CounterMode,选择新的计数器模式。

然后再往下,TIM_ARRPreloadConfig函数,自动重装器预装功能配置。

然后再往下,TIM_SetCounter函数,给计数器写入一个值。如果你想要手动给一个计数值,就可以用这个函数。

然后再往下,TIM_SetAutoreload函数,给自动重装器写一个值。

再往下,TIM_GetCounter函数,获取当前计数器的值。

再往下,TIM_GetPrescaler函数,获取当前预分频器的值。

最后,TIM_GetFlagStatus、TIM_ClearFlag、TIM_GetITStatus以及TIM_ClearITPendingBit函数,这些函数就是用来获取标志位和清除标志位的,

好,现在开始写代码,在这里我准备初始化的是TIM2,也就是通用定时器。

1. RCC开启时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

这里注意,要使用APB1的开启时钟函数,因为TIM2是APB1总线的外设。

2. 选择时基单元时钟

TIM_InternalClockConfig(TIM2);

在这里我们选择内部时钟源,这样TIM2的时基单元就由内部时钟来驱动了。

3. 配置时基单元

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

各个参数解析如下:

  • TIM_ClockDivision :指定时钟分频。在这里选择TIM_CKD_DIV1,1分频。
  • TIM_CounterMode :计数模式。在这里选择向上计数模式TIM_CounterMode_Up。
  • TIM_Period :ARR自动重装器的值。
  • TIM_Prescaler :PSC预分频器的值。
  • TIM_RepetitionCounter :重复计数器的值。这个是高级定时器才有的,我们不需要用,直接给0即可。

在这里定时时间为1s,计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

4. 配置输出中断控制

TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

注意:在是能TIM2之前手动清除一下更新中断标志位,这样可以避免刚初始化完就进入中断的问题。

5. 配置NVIC

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;
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);

1.4.2 编写定时器中断函数

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

定时中断完整工程:TIM定时中断之定时器定时中断应用案例

二、定时器外部时钟应用案例

2.1 应用案例简介

本案例实现了一个定时器外部时钟的功能。该程序使用了外部时钟来驱动定时器,我们可以在定时器指定的外部引脚上输入一个方波信号来提供定时器计数的时钟,在这里使用的是对射式红外传感器来手动模拟一个外部时钟。当我们用挡光片依次遮挡、移开、遮挡、移开这样的操作来提供一个方波信号时,可以看到OLED上这个CNT就是定时器中计数器的值。每遮挡移开一次,计数器加1,然后计数器记到9后自动清零,同时申请中断,执行Num++。(使用定时器的外部时钟,可以提供一个更加精准的时钟来计时,或者也可以把外部时钟当作一个计数器用来统计引脚上电平翻转的次数,毕竟定时器本质上就是一个计数器对吧)

2.2 电路接线图

在这里插入图片描述

2.3 应用案例代码

定时器头文件Timer.h:

#ifndef __TIMER_H
#define __TIMER_Hvoid Timer_Init(void);
uint16_t Timer_GetCounter(void);#endif

定时器实现文件Timer.c:

#include "stm32f10x.h"                  // Device headervoid Timer_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;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);TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);TIM_ClearFlag(TIM2, TIM_FLAG_Update);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;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);
}uint16_t Timer_GetCounter(void)
{return TIM_GetCounter(TIM2);
}/*
void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
*/

主程序文件main.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"uint16_t Num;int main(void)
{OLED_Init();Timer_Init();OLED_ShowString(1, 1, "Num:");OLED_ShowString(2, 1, "CNT:");while (1){OLED_ShowNum(1, 5, Num, 5);OLED_ShowNum(2, 5, Timer_GetCounter(), 5);}
}void TIM2_IRQHandler(void)
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){Num ++;TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

2.4 应用案例分析

整体思路基本跟定时中断的案例一致,区别只是时钟源的选择以及多了一个GPIO的初始化,这里就不再累述了。

定时器外部时钟完整工程:TIM定时中断之定时器外部时钟应用案例



http://www.ppmy.cn/devtools/97646.html

相关文章

鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务

在鸿蒙的内核线程就是任务,系列篇中说的任务和线程当一个东西去理解. 一般二种场景下需要切换任务上下文: 在线程环境下,从当前线程切换到目标线程,这种方式也称为软切换,能由软件控制的自主式切换.哪些情况下会出现软切换呢? 运…

自养号测评技术:如何挑选适合的IP环境方案

市面上的IP服务及常见问题 当前市场上常见的IP服务包括911、Luminati、Google Fi、TM流量卡、Socks专线等。这些服务在为用户提供网络代理或VPN服务时,常会遇到以下主要问题: 1. 高负载与重复率高:由于使用人数众多,导致网络拥堵…

PostgreSQL vacuum freeze

一、简介 数据库使用 32 位事务号,最大容纳 42 亿左右的事务号,事务号是循环使用的。当前事务号过去的 21 亿事务属于过去的事务号,当前事务号往前的 21亿 属于未来的事务号,未来的事务号对当前事务是不可见的。当事务号处于未来事…

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(一)---UnrealCV获取深度+分割图像

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程使用的环境: ubuntu 22.04 ros2 humblewindows11 UE5.4.3python8 本系列教程将涉及以…

基于深度学习的环境感知系统

基于深度学习的环境感知系统是一类能够理解、感知和解读周围环境的智能系统。通过使用深度学习算法,这些系统可以分析多模态数据(如图像、音频、激光雷达数据等),实时感知环境的动态变化,为自动驾驶、机器人、智能家居…

Zookeeper详解以及常见的高可用关联组件

一、ZooKeeper 详解 Apache ZooKeeper 是一个开源的分布式协调服务,用于分布式应用程序之间的协调和管理。ZooKeeper 提供了一个高效、可靠的服务来帮助管理分布式系统中的共享配置信息、命名、同步和组服务等。 二、主要特性 1. 高可用性 ZooKeeper 集群通过选…

Spring Boot 打成的 jar 和普通的 jar

Spring Boot 打包的 JAR 文件与普通的 JAR 文件有几个关键区别。Spring Boot 的 JAR 文件是“可执行 JAR 文件”(也称为 fat JAR 或 uber JAR),它包含了应用运行所需的所有依赖、资源以及内嵌的服务器(如 Tomcat 或 Jetty&#xf…

6、JUC并发同步工具类应用与实战

JUC并发同步工具类应用与实战 常用并发同步工具类应用场景ReentrantLockReentrantLock常用APILock接口基本语法工作原理 ReentrantLock使用独占锁:模拟抢票场景公平锁和非公平锁可重入锁Condition详解结合Condition实现生产者消费者模式 ReentrantLock应用场景总结 …