STM32定时器(TIM)详解:从基本概念到代码实践
STM32定时器(TIM)详解:从基本概念到代码实践
STM32定时器(TIM)是STM32微控制器中一个非常重要的外设,广泛应用于各种定时、计数和PWM信号生成等场景。本文将详细介绍STM32定时器的基本概念、不同类型定时器的功能特点,以及如何通过定时中断实现精确的时间控制。
1. TIM简述
定时器的基本功能是在预定的时间间隔内产生周期性的中断。例如,定时器可以被设置为每1ms产生一个中断信号,这常用于创建周期性的服务例程,如操作系统的时钟滴答。
STM32的定时器通常包含一个16位的计数器,这意味着它能够计数从0到65535(2^16 - 1)。当计数器从0计数到预设值时,可生成中断或其他事件。在72MHz的时钟频率下,如果定时器的预分频器(Prescaler)设为72-1(即每72个时钟周期计数一次),则计数器每计数到72000就相当于过去了1秒。这样就可以用来测量时间,或创建延时等。并且最大定时为59.65s。
定时器可以从不同的时钟源中选择,例如内部的主时钟或外部时钟源。定时器可以在多种模式下工作,包括简单的定时模式(如计数溢出时产生中断),PWM产生模式(用于调整电压输出,控制电机速度等),输入捕获模式(测量外部事件的时间间隔,如信号的频率)等。
定时器还可以配置为触发ADC(模数转换器)的启动,或与其他定时器同步等。
2. 定时器类型
STM32的定时器主要分为三种类型:
高级定时器:TIM1、TIM8,位于APB2总线上。这类定时器拥有全部的功能,例如高级PWM控制,还支持三相电机的正反变换,复杂的同步控制,以及与其他高级功能的集成,如直接数字控制转换(DCC)。并且额外具有重复计数器、死区生成、互补输出、刹车输入等功能。
通用定时器:TIM2、TIM3、TIM4、TIM5,位于APB1总线上。拥有基本定时器的全部功能,并且额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。
基本定时器:TIM6、TIM7,位于APB1总线上。拥有定时中断、主从触发DAC的功能。
对于STM32F103C8T6,拥有的定时器资源为:TIM1、TIM2、TIM3、TIM4,所以在使用任何外设时,要先查明这个芯片有没有这个功能。
2.1 基本定时器
基本定时器的核心组件包括预分频器、计数器和自动重装载寄存器。预分频器对72MHz的计数时钟进行预分频,计数器可以对预分频后的计数时钟进行计数,自动重装载寄存器存储计数目标值。当计数器的值等于自动重装值时,就会产生中断信号。
2.2 通用定时器
通用定时器相比基本定时器增加了更多功能。它不仅支持向上计数模式,还支持向下计数模式和中央对齐计数模式。此外,通用定时器还支持外部时钟源,并且具有输入捕获和输出比较电路。
通用定时器结构示意图
2.3 高级定时器
高级定时器相比通用定时器增加了更多功能。它增加了重复次数计数器,可以实现每隔几个计数周期才会发生一次更新事件和更新中断。此外,高级定时器还具有死区生成电路、互补输出和刹车输入等保护机制。
3. 定时中断
定时中断的实现需要配置预分频器、计数器和自动重装载寄存器。预分频器的参数变化不会立刻生效,而是等到本次计数周期完成,产生了更新事件,改变后的参数才会起作用。计数器的计数频率和溢出频率可以通过以下公式计算:
- 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
- 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
为了防止在运行途中更改造成错误,引入了影子寄存器,目的是让值的变化和更新事件同步发生。
4. 代码示例
4.1 内部时钟定时中断
下面是一个使用内部时钟的定时中断示例。代码主要分为以下几个步骤:
- 开启RCC时钟
- 选择时基单元的时钟源
- 配置时基单元
- 配置输出中断控制
- 配置NVIC
- 运行控制
#include "stm32f10x.h"
//定时中断初始化
void Timer_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
//配置时钟源
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
//时基单元初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
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_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
//中断输出配置
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
//NVIC中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//NVIC配置
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
//TIM使能
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
4.2 外部时钟定时中断
下面是一个使用外部时钟的定时中断示例。代码主要分为以下几个步骤:
- 开启RCC时钟
- GPIO初始化
- 外部时钟配置
- 时基单元初始化
- 中断输出配置
- NVIC中断分组和NVIC配置
- TIM使能
#include "stm32f10x.h"
//定时中断初始化
void Timer_Init(void)
{
//开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//GPIO初始化
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); //将PA0引脚初始化为上拉输入
//外部时钟配置
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; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
//中断输出配置
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
//NVIC中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//NVIC配置
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
//TIM使能
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
//返回定时器CNT的值
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2); //返回定时器TIM2的CNT
}
通过以上代码示例,读者可以了解如何配置STM32定时器实现定时中断功能。这些代码可以作为嵌入式系统开发中的基础参考,帮助开发者快速上手STM32定时器的使用。