江协stm32学习:6-1~6-2 TIM定时中断(基本定时功能)
目录
江协stm32学习:6-1~6-2 TIM定时中断(基本定时功能)
2.2.3 配置定时器的自动重装载寄存器(ARR)的预装载功能
2.2.3 配置定时器的自动重装载寄存器(ARR)的预装载功能
一、TIM的四种功能
第一部分:定时器基本定时功能。实现每隔一段固定时间可以执行一段程序
第二部分:定时器输出比较的功能。最常用的用途就是产生PWM波形,用于驱动电机等设备
第三部分:定时器输入捕获的功能。学习使用输入捕获这个模块来实现测量方波频率的例子
第四部分:定时器的编码器接口。使用该接口可以更加方便地使用正交编码器的输出波形
本章讲述第一部分,后面三种,看之后的三篇博客
二、TIM(Timer)定时器简介
1、基本介绍
-
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。所以定时器其实是一个计数器,而当这个计数器的输入是一个准确可靠的基准时钟的时候,那么他对这个基准时钟进行技术的过程实际上就是计时的过程。而在stm32中定时器的基准时钟一般是主频72MHz,则对72MHz计72个数,那就是1MHz也就是1us的时间。
-
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
-
根据复杂度和应用场景分为了高级定时器、通用定时器(最常用)、基本定时器三种类型
编号是由于一个芯片一般都有很多个定时器。STM32F103C8T6定时器资源是TIM1~TIM4
2、定时器结构
1)基本定时器结构
1.1 定时中断功能电路
- 连接基准计数时钟:CK_INT 是 STM32 内部的高速时钟源,通常是内部 RC 振荡器产生的时钟信号,其频率相对稳定,一般在几兆赫到几十兆赫之间,为芯片的基本操作提供时钟基础,也是很多其他时钟源的基准。
当系统配置为使用 CK_INT 作为系统时钟(HCLK)时,HCLK 会进一步作为 APB1 总线时钟(PCLK1)的输入源。通过 APB1 预分频器对 HCLK 进行分频,得到 PCLK1,在此处,库函数中设置 APB1 预分频的系数是 2,所以APB1 总线的时钟频率(PCLK1)是72MHz÷2=36MHz。
之后,RCC 根据系统的配置(如 APB1 预分频系数)计算出 TIMxCLK 的频率,并将其提供给定时器使用,并且当 APB1 预分频系数不是 1 时,TIMxCLK 的频率会被设置为 PCLK1 的两倍,故而这儿×2,36MHz×2=72MHz,定时器的基准时钟频率为72MHz。
再传输给时基单元 - 时基电路:16位计数器(执行计数定时的一个寄存器)、预分频器(对计数器的时钟进行分频使得计数更加灵活)、自动重装寄存器(计数的目标值)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时(如果想要时间再长一点,stm32定时器支持级联的方式)
预分频器:写x,实际分频系数+1,则分频后的为72/(x+1)MHz。预分频器是16位的,所以MAX可以写65535,也就是65536分频。
计数器:16位,值0~65535,再加会回0。计数器的值不断自增运行,自增的时候不断与自动重装寄存器比较,当自增运行到目标值的时候,产生中断,那就完成了定时任务。
自动重装寄存器:计数器达到目标值的时候,产生中断信号,并且清零计数器。计数值等于自动重装值产生的中断,叫做更新中断,更新中断之后就会通往NVIC,那么我们在配置好NVIC的定时器通道后,定时器的更新中断就能够得到CPU的响应了。更新事件不会触发中断,但是会触发其他电路的工作。
1.2 主模式触发DAC功能电路
这个功能可以让内部的程序在不受程序的控制下自动运行,控制好可以极大减轻CPU的负担
由TRGO直接触发DAC,可以避免频繁中断CPU,且整个过程不需要软件参与,实现了硬件自动化
2)通用定时器结构
2.1 结构图
2.2 计数模式
计数模式 | 基本原理 | 典型应用场景 |
---|---|---|
向上计数模式 | 从最低值开始,每个计数周期加 1,直到达到目标值,也就是最大值(如 8 位为 255),触发中断,然后清零,重新开始 | 电子时钟的秒数计数 |
向下计数模式 | 从目标值开始,每个计数周期减 1,直到达到最低值,也就是0,触发中断,然后回到目标值,重新开始 | 定时关机功能的倒计时控制 |
双向计数模式(中央对齐) | 从0每个计数周期加 1到MAX,触发一次;从MAX每个计数周期减 1到0,触发一次 | 电机控制中对称波形信号的生成 |
3)高级定时器结构
3.1 结构图
STM32通用/高级定时器理论结构体讲解 这一篇讲的比较细,想更加深入理解定时器结构可以考虑
三、定时中断结构
1、整体结构
- 计数器计数频率:CK_CNT=CK_PSC/(PSC+1)
- 计数器溢出频率:CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)
2、时钟树
时钟树就是stm32中用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统。时钟是所有外设的基础,也是最先需要配置的东西。程序中主函数之前其实还会执行一个SystemInit函数,这个函数当中配置了个时钟树,是由St公司帮我们写好了,但是有需要的话也可以进行修改。
SystemInit函数里面的配置是:首先启动内部时钟,先选择内部8MHz的时钟来运行,然后再启动外部时钟,外部时钟进入PLL倍频,8MHz倍频9倍,得到72MHz,等到锁相环输出稳定后,选择锁相环输出为系统时钟。
LSI(内部低速时钟)、LSE(外部低速时钟)、HSI(内部高速时钟)、HSE(外部高速时钟)
嵌入式学习笔记——STM32的时钟树 这个博主小向是个Der的这篇文我觉得讲的相当详细,完全掰碎了来讲的,可以看看,写的很好。
四、程序编写
1、定时中断(默认使用内部时钟)
1)步骤(对照着基本结构打通电路)
Step 1 RCC开启时钟。在这打开时钟后定时器的基准时钟和整个外设的工作时钟都会同时打开。
Step 2 选择时基单元的时钟源。对于定时中断,选择内部时钟源。
Step 3 配置时基单元。可以用一个结构体参数配置好PSC、ARR、CNT。
Step 4 配置输出中断控制。允许更新中断输出到NVIC。
Step 5 配置NVIC,在NVIC中打开定时器中断通道并分配一个优先级。
Step 6 运行控制,整个模块配置完成后,还需要使能一下计数器。
2)定时器TIM的库函数
2.1 初始化函数
- 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_ETRCClockModelConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPRescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
- void TIM_ETRCClockMode2Config(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 | 初始化定时器的基本参数(计数模式、分频系数、重装载寄存器值等) | 第一个参数:选择定时器;第二个参数:指向 TIM_TimeBaseInitTypeDef 结构体的指针,包含定时器基本配置参数 |
TIM_TimeBaseStructInit | 填充 TIM_TimeBaseInitTypeDef 结构体为默认参数 | 结构体默认值来初始化参数 |
TIM_Cmd | 启动或停止定时器 | 第一个参数:选择定时器;第二个参数:指定定时器的新状态(ENABLE 或 DISABLE) |
TIM_ITConfig | 配置定时器的中断使能或禁用 | 第一个参数:选择定时器;第二个参数:指定的中断类型;第三个参数:指定中断的新状态(ENABLE 或 DISABLE) |
TIM_InternalClockConfig | 配置定时器为内部时钟 | 选择定时器 |
TIM_ITRxExternalClockConfig | 配置定时器为从内部触发输入(ITR)的外部时钟 | 第一个参数:选择定时器;第二个参数:内部触发输入源 |
TIM_TIxExternalClockConfig | 配置定时器为从 TIx 引脚的外部时钟 | 第一个参数:选择定时器;第二个参数:TIx 引脚源;第三个参数:输入捕获极性;第四个参数:输入滤波器值 |
TIM_ETRCClockModelConfig | 配置定时器为从 ETRC 引脚的外部时钟模式1 | 第一个参数:选择定时器;第二个参数:外部触发 prescaler;第三个参数:外部触发极性;第四个参数:外部触发滤波器 |
TIM_ETRCClockMode2Config | 配置定时器为从 ETRC 引脚的外部时钟模式2 | 第一个参数:选择定时器;第二个参数:外部触发 prescaler;第三个参数:外部触发极性;第四个参数:外部触发滤波器 |
TIM_ETRConfig | 配置定时器为从 ETR 引脚的外部触发 | 第一个参数:选择定时器;第二个参数:外部触发 prescaler;第三个参数:外部触发极性;第四个参数:外部触发滤波器 |
黄色格子:时钟源选择的六个函数。 粉色格子:时基单元。
蓝色格子:中断输出控制。 绿色格子:运行控制
2.2 更改某些关键参数
2.2.1 配置定时器的预分频器
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
TIMx:定时器的基地址(如TIM1、TIM2等)。(具体对应什么看上面的基本介绍部分)
Prescaler:预分频器的值,它决定了计数器的时钟频率为输入时钟频率除以(Prescaler + 1)。
TIM_PSCReloadMode:预分频器重载模式,决定预分频器在何时更新。可以是以下值:
0x00:预分频器在计数器停用时更新。
0x01:预分频器在计数器启用时立即更新。
2.2.2 配置定时器的计数模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
TIMx:定时器的基地址(如TIM1、TIM2等)。
TIM_CounterMode:计数模式,可以是以下值:
0x0000:向上计数模式。
0x0010:向下计数模式。
0x0020:中心对齐模式1。
0x0040:中心对齐模式2。
0x0060:中心对齐模式3。
2.2.3 配置定时器的自动重装载寄存器(ARR)的预装载功能
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
TIMx:定时器的基地址(如TIM1、TIM2等)。
NewState:指定定时器的新状态,可以是以下值:
ENABLE:开启预装载功能,对ARR的写操作将在下一个更新事件时生效。
DISABLE:关闭预装载功能,对ARR的写操作立即生效。
基于STM32的自动重装载auto-reload preload以及影子寄存器 自动重装寄存器有什么用可以看这个,有和51对比着来讲
2.2.3 配置定时器的自动重装载寄存器(ARR)的预装载功能
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
TIMx:定时器的基地址(如TIM1、TIM2等)。
NewState:指定定时器的新状态,可以是以下值:
ENABLE:开启预装载功能,对ARR的写操作将在下一个更新事件时生效。
DISABLE:关闭预装载功能,对ARR的写操作立即生效。
2.2.4 配置定时器的自动重装载寄存器(ARR)的值
这个值决定了定时器计数到该值后会产生更新事件,进而影响定时器的计数周期。
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
TIMx:定时器的基地址(如TIM1、TIM2等)。
Autoreload:要设置的自动重装载寄存器的值。这个值决定了定时器的计数周期。
2.2.5 配置设置定时器的当前计数值
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
TIMx:定时器的基地址(如TIM1、TIM2等)。
Counter:要设置的计数值。
2.2.6 查阅当前值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx); //获取定时器的当前计数值。
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx); //获取定时器的预分频器的值。
TIMx:定时器的基地址(如TIM1、TIM2等)。
2.2.7 有关标志位
第一个参数都是定时器的基地址(如TIM1、TIM2等)
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
函数名称 | 函数功能 | 参数说明 |
---|---|---|
TIM_GetFlagStatus | 获取指定定时器标志的状态 | 指定的标志 |
TIM_ClearFlag | 清除指定的定时器标志 | 要清除的标志 |
TIM_GetITStatus | 获取指定定时器中断的状态 | 指定的中断类型 |
TIM_ClearITPendingBit | 清除指定定时器中断的挂起位 | 要清除挂起位的中断类型 |
#include "stm32f10x.h" // 包含 STM32 标准库头文件
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
void TIM2_Config(void)
{
// 启用定时器 2 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//TIM_InternalClockConfig(TIM2);//配置为使用内部时钟,but一般默认为内部时钟,不写也OK
// 配置定时器基本参数
TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装值,定时周期(根据时钟频率计算)
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频器,定时器时钟频率 = APB1 时钟频率 / (TIM_Prescaler + 1)
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频因子,取 0 表示不分频
TIM_TimeBaseStructure_C.TIMounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 初始化定时器基本参数
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清楚标志位,以免一上电就进入中断 //①
// 配置定时器中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断
// 配置中断优先级并使能中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 定时器 2 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 启用中断
NVIC_Init(&NVIC_InitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
// 定时器 2 中断服务函数
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) // 判断是否为更新中断
{
// 在这里添加定时器中断处理代码,例如倒计时操作、状态切换等
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志位
}
}
/* 在 main 函数中调用定时器配置函数 */
int main(void)
{
// 系统时钟配置等其他初始化代码
TIM2_Config(); // 配置定时器
while (1)
{
// 主循环代码
}
}
※ TimeBaseInit函数会生成一个更新事件,来立刻重新装载预分频器和重复计数器的值,这是因为我们写的值只有在更新事件时才会真正起作用,这里为了让值立刻起效,在最后手动生成了一个更新事件,but这样的弊端是更新事件和更新中断是同时发生的,更新中断会更新中断标志位,所以当初始化完成,更新中断立刻进入,执行一次中断函数。——刚一上电就立刻进入中断的原因。解决方法是:TimeBaseInit函数后,开启中断前, TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清楚标志位,以免一上电就进入中断
※ 使用跨文件的变量
法1:用extern,在外部文件的.c文件上方使用extern声明一次变量
法2:将中断函数直接复制到主函数下
2、定时中断+外部时钟
配置时钟的时候改为外部时钟TIM_ETRCClockMode2Config即可
注意:STM32 定时器的 ETR 引脚本质上是一个 GPIO 引脚,它是被复用为定时器的外部触发输入引脚。所以这儿还需要加上配置GPIO。
这儿配置GPIO的时候,手册给的推荐是浮空输入,but一旦悬空电平就会跳个不停,所以除非外部输入i新年好功率很小,内部上拉电阻可能会影响输入信号,否则建议使用上拉输入
#include "stm32f10x.h" // 包含 STM32 标准库头文件
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
void TIM2_Config(void)
{
// 使能 GPIO 时钟和定时器时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 配置 GPIOA 的引脚 0(ETR)为复用模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_ETRCClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);//配置为使用内部时钟,but一般默认为内部时钟,不写也OK
// 配置定时器基本参数
TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装值,定时周期(根据时钟频率计算)
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频器,定时器时钟频率 = APB1 时钟频率 / (TIM_Prescaler + 1)
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分频因子,取 0 表示不分频
TIM_TimeBaseStructure_C.TIMounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 初始化定时器基本参数
TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清楚标志位,以免一上电就进入中断 //①
// 配置定时器中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断
// 配置中断优先级并使能中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 定时器 2 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 启用中断
NVIC_Init(&NVIC_InitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
// 定时器 2 中断服务函数
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) // 判断是否为更新中断
{
// 在这里添加定时器中断处理代码,例如倒计时操作、状态切换等
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志位
}
}
/* 在 main 函数中调用定时器配置函数 */
int main(void)
{
// 系统时钟配置等其他初始化代码
TIM2_Config(); // 配置定时器
while (1)
{
// 主循环代码
}
}